Python类与对象
Posted HT . WANG
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Python类与对象相关的知识,希望对你有一定的参考价值。
1.改变对象的字符串显示
要改变一个实例的字符串表示,可重新定义它的 __str__()
和 __repr__()
方法
class Pair:
def __init__(self, x, y):
self.x = x
self.y = y
def __repr__(self):
return 'Pair(0.x!r, 0.y!r)'.format(self)
def __str__(self):
return '(0.x!s, 0.y!s)'.format(self)
>>> p = Pair(3, 4)
>>> p
Pair(3, 4) # __repr__() output
>>> print(p)
(3, 4) # __str__() output
>>>
!r
格式化代码指明输出使用 __repr__()
来代替默认的 __str__()
>>> p = Pair(3, 4)
>>> print('p is 0!r'.format(p))
p is Pair(3, 4)
>>> print('p is 0'.format(p))
p is (3, 4)
>>>
2.自定义字符串的格式化
为了自定义字符串的格式化,我们需要在类上面定义 __format__()
方法
_formats =
'ymd' : 'd.year-d.month-d.day',
'mdy' : 'd.month/d.day/d.year',
'dmy' : 'd.day/d.month/d.year'
class Date:
def __init__(self, year, month, day):
self.year = year
self.month = month
self.day = day
def __format__(self, code):
if code == '':
code = 'ymd'
fmt = _formats[code]
return fmt.format(d=self)
>>> d = Date(2012, 12, 21)
>>> format(d)
'2012-12-21'
>>> format(d, 'mdy')
'12/21/2012'
>>> 'The date is :ymd'.format(d)
'The date is 2012-12-21'
>>> 'The date is :mdy'.format(d)
'The date is 12/21/2012'
>>>
3.让对象支持上下文管理协议
写一个表示网络连接的类:
from socket import socket, AF_INET, SOCK_STREAM
class LazyConnection:
def __init__(self, address, family=AF_INET, type=SOCK_STREAM):
self.address = address
self.family = family
self.type = type
self.sock = None
def __enter__(self):
if self.sock is not None:
raise RuntimeError('Already connected')
self.sock = socket(self.family, self.type)
self.sock.connect(self.address)
return self.sock
def __exit__(self, exc_ty, exc_val, tb):
self.sock.close()
self.sock = None
from functools import partial
conn = LazyConnection(('www.python.org', 80))
# Connection closed
with conn as s:
# conn.__enter__() executes: connection open
s.send(b'GET /index.html HTTP/1.0\\r\\n')
s.send(b'Host: www.python.org\\r\\n')
s.send(b'\\r\\n')
resp = b''.join(iter(partial(s.recv, 8192), b''))
# conn.__exit__() executes: connection closed
分析上述代码。但是初始化的时候并不会做任何事情(比如它并没有建立一个连接)。 连接的建立和关闭是使用 with
语句自动完成的
当出现 with
语句的时候,对象的 __enter__()
方法被触发, 如果有返回值会被赋值给 as
声明的变量。然后,with
语句块里面的代码开始执行。 最后with语句块执行结束,__exit__()
方法被触发进行清理工作。
4.创建大量对象时节省内存方法
class Date:
__slots__ = ['year', 'month', 'day']
def __init__(self, year, month, day):
self.year = year
self.month = month
self.day = day
定义 __slots__
后,Python就会为实例使用一种更加紧凑的内部表示。 实例通过一个很小的固定大小的数组来构建,而不是为每个实例定义一个字典。 在 __slots__
中列出的属性名在内部被映射到这个数组的指定下标上。 使用slots一个不好的地方就是我们不能再给实例添加新的属性了,只能使用在 __slots__
中定义的那些属性名。
5.在类中封装属性名
Python语言并没有类似于c++的访问控制(public private,protected)
通过遵循一定的属性和方法命名规约来达到这个效果
(1)任何以单下划线_开头的名字都应该是内部实现
class A:
def __init__(self):
self._internal = 0 # An internal attribute
self.public = 1 # A public attribute
def public_method(self):
'''
A public method
'''
pass
def _internal_method(self):
pass
(2)双下划线保证继承过程中不会被覆盖
class B:
def __init__(self):
self.__private = 0
def __private_method(self): #私有属性会被分别重命名_B__private 和 _B__private_method
pass
class C(B):
def __init__(self):
super().__init__()
self.__private = 1
# Does not override B.__private_method()
def __private_method(self): #私有属性会被分别重命名_C__private 和 _C__private_method
pass
6.调用父类方法
想在子类中调用父类的某个已经被覆盖的方法。为了调用父类(超类)的一个方法,可以使用 super()
函数
class A:
def spam(self):
print('A.spam')
class B(A):
def spam(self):
print('B.spam')
super().spam() # Call parent spam()
(1)常见用法是在 __init__()
方法中确保父类被正确的初始化
class A:
def __init__(self):
self.x = 0
class B(A):
def __init__(self):
super().__init__()
self.y = 1
(2)常见用法出现在覆盖Python特殊方法的代码
class Proxy:
def __init__(self, obj):
self._obj = obj
# Delegate attribute lookup to internal obj
def __getattr__(self, name):
return getattr(self._obj, name)
# Delegate attribute assignment
def __setattr__(self, name, value):
if name.startswith('_'):
super().__setattr__(name, value) # 如果某个属性名以下划线(_)开头,就通过 super() 调用原始的 __setattr__()
else: # 否则的话就委派给内部的代理对象 self._obj 去处理
setattr(self._obj, name, value)
注意:
class Base:
def __init__(self):
print('Base.__init__')
class A(Base):
def __init__(self):
Base.__init__(self)
print('A.__init__')
class B(Base):
def __init__(self):
Base.__init__(self)
print('B.__init__')
class C(A,B):
def __init__(self):
A.__init__(self)
B.__init__(self)
print('C.__init__')
>>> c = C()
Base.__init__
A.__init__
Base.__init__ #发现 Base.__init__() 被调用两次
B.__init__
C.__init__
>>>
class Base:
def __init__(self):
print('Base.__init__')
class A(Base):
def __init__(self):
super().__init__()
print('A.__init__')
class B(Base):
def __init__(self):
super().__init__()
print('B.__init__')
class C(A,B):
def __init__(self):
super().__init__() # Only one call to super() here
print('C.__init__')
>>> c = C()
Base.__init__ #每个 __init__() 方法只会被调用一次
B.__init__
A.__init__
C.__init__
>>>
合并所有父类的MRO列表并遵循如下三条准则:
- 子类会先于父类被检查
- 多个父类会根据它们在列表中的顺序被检查
- 如果对下一个类存在两个合法的选择,选择第一个父类
7.创建新的类或实例属性
一个描述器就是一个实现了三个核心的属性访问操作(get, set, delete)的类, 分别为 __get__()
、__set__()
和 __delete__()
这三个特殊的方法
class Integer:
def __init__(self, name):
self.name = name
def __get__(self, instance, cls):
if instance is None:
return self
else:
return instance.__dict__[self.name]
def __set__(self, instance, value):
if not isinstance(value, int):
raise TypeError('Expected an int')
instance.__dict__[self.name] = value
def __delete__(self, instance):
del instance.__dict__[self.name]
为了使用一个描述器,需将这个描述器的实例作为类属性放到一个类的定义中
class Point:
x = Integer('x')
y = Integer('y')
def __init__(self, x, y):
self.x = x
self.y = y
# 所有对描述器属性(比如x或y)的访问会被 __get__() 、__set__() 和 __delete__() 方法捕获到
>>> p = Point(2, 3)
>>> p.x # Calls Point.x.__get__(p,Point)
2
>>> p.y = 5 # Calls Point.y.__set__(p, 5)
8.使用延迟计算属性
class lazyproperty:
def __init__(self, func):
self.func = func
def __get__(self, instance, cls):
if instance is None:
return self
else:
value = self.func(instance)
setattr(instance, self.func.__name__, value)
return value
import math
class Circle:
def __init__(self, radius):
self.radius = radius
@lazyproperty #装饰器lazyproperty
def area(self):
print('Computing area')
return math.pi * self.radius ** 2
@lazyproperty #同名装饰器lazyproperty 利用这一点,使用 __get__() 方法在实例中存储计算出来的值, 这个实例使用相同的名字作为它的property。 这样一来,结果值被存储在实例字典中并且以后就不需要再去计算这个property了
def perimeter(self):
print('Computing perimeter')
return 2 * math.pi * self.radius
>>> c = Circle(4.0)
>>> c.radius
4.0
>>> c.area
Computing area #消息 Computing area 和 Computing perimeter 仅仅出现一次
50.26548245743669
>>> c.area
50.26548245743669
>>> c.perimeter
Computing perimeter
25.132741228718345
>>> c.perimeter
25.132741228718345
>>>
9.定义接口或者抽象基类
抽象类的一个特点是它不能直接被实例化
抽象类的目的就是让别的类继承它并实现特定的抽象方法
from abc import ABCMeta, abstractmethod
class IStream(metaclass=ABCMeta): #使用 abc 模块可以很轻松的定义抽象基类
@abstractmethod
def read(self, maxbytes=-1):
pass
@abstractmethod
def write(self, data):
pass
class SocketStream(IStream): #(1)类继承它并实现抽象基类特定的抽象方法
def read(self, maxbytes=-1):
pass
def write(self, data):
pass
import io
#(2)也可以通过注册方式来让某个类实现抽象基类
IStream.register(io.IOBase)
10.实现自定义容器
实现一个自定义的类来模拟内置的容器类功能,比如列表和字典
import collections
import bisect
class SortedItems(collections.Sequence): #继承Sequence抽象类
def __init__(self, initial=None):
self._items = sorted(initial) if initial is not None else [] #初始化为有序序列
# Required sequence methods
def __getitem__(self, index):
return self._items[index]
def __len__(self):
return len(self._items)
# Method for adding an item in the right location
def add(self, item):
bisect.insort(self._items, item)
items = SortedItems([5, 1, 3])
print(list(items))
print(items[0], items[-1])
items.add(2)
print(list(items))
'''
[1, 3, 5]
(1, 5)
[1, 2, 3, 5]
'''
持所有常用操作,包括索引、迭代、包含判断,甚至是切片操作。
11.属性的代理访问
将某个实例的属性访问代理到内部另一个实例中去,代理是一种编程模式,它将某个操作转移给另外一个对象来实现
(1)简单代理 继承方法重写
class A:
def spam(self, x):
pass
def foo(self):
pass
class B1:
"""简单的代理"""
def __init__(self):
self._a = A()
def spam(self, x):
# Delegate to the internal self._a instance
return self._a.spam(x)
def foo(self):
# Delegate to the internal self._a instance
return self._a.foo()
def bar(self):
pass
(2)如果有大量的方法需要代理, 那么使用 __getattr__()
方法或许或更好些:
class A:
def spam(self, x):
pass
def foo(self):
pass
class B2:
"""使用__getattr__的代理,代理方法比较多时候"""
def __init__(self):
self._a = A()
def bar(self):
pass
# Expose all of the methods defined on class A
def __getattr__(self, name):
#这个方法在访问的属性方法不存在的时候被调用
return getattr(self._a, name)
b = B()
b.bar()
b.spam(42) # 调用 B.__getattr__('spam') B中没有该方法 从A中找
注意:
__getattr__()
只有在属性不存在时才会调用。 因此,如果代理类实例本身有这个属性的话,那么不会触发这个方法的
12.在类中定义多个构造器
想实现一个类,除了使用 __init__()
方法外,还有其他方式可以初始化它
import time
class Date:
# Primary constructor
def __init__(self, year, month, day):
self.year = year
self.month = month
self.day = day
# Alternate constructor
@classmethod
def today(cls):
t = time.localtime()
return cls(t.tm_year, t.tm_mon, t.tm_mday)
a = Date(2012, 12, 21)
print(a.year,a.month,a.day)
b = Date.today()
print(b.year,b.month,b.day)
'''
(2012, 12, 21)
(2022, 5, 3)
'''
13.创建不调用init方法的实例
可以通过 __new__()
方法创建一个未初始化的实例
class Date:
def __init__(self, year, month, day):
self.year = year
self.month = month
self.day = day
d = Date.__new__(Date) #不调用 __init__() 方法 来创建这个Date实例
data = 'year':2012, 'month':8, 'day':29#需要手动初始化
for key, value in data.items():
setattr(d, key, value)
print(d.year,d.month,d.day)
14.通过字符串调用对象方法
调用一个方法实际上是两步独立操作,第一步是查找属性,第二步是函数调用
(方法1)为了调用某个方法,你可以首先通过 getattr()
来查找到这个属性,然后再去以函数方式调用它即可
import math
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __repr__(self):
return 'Point(!r:,!r:)'.format(self.x, self.y)
def distance(self, x, y):
return math.hypot(self.x - x, self.y - y)
p = Point(2, 3)
d = getattr(p, 'distance')(0, 0) # 点(2,3)到点(0,0)距离
(方法2)operator.methodcaller()
创建一个可调用对象,并同时提供所有必要参数, 然后调用的时候只需要将实例对象传递给它即可
import math
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __repr__(self):
return 'Point(!r:,!r:)'.format(self.x, self.y)
def distance(self, x, y):
return math.hypot(self.x - x, self.y - y)
import operator
operator.methodcaller('distance', 0, 0)(p)
15.实现访问者模式
当计算1 + 2 * (3 - 4) / 5 涉及不同类型的加减乘除
设计一个运算基类:
class Node:
pass
class UnaryOperator(Node):
def __init__(self, operand):
self.operand = operand
class BinaryOperator(Node):
def __init__(self, left, right):
self.left = left
self.right = right
class Add(BinaryOperator):
pass
class Sub(BinaryOperator):
pass
class Mul(BinaryOperator):
pass
class Div(BinaryOperator):
pass
class Negate(UnaryOperator):
pass
class Number(Node):
def __init__(self, value):
self.value = value
但对于每个不同类型的表达式,每次都要重新定义一遍
设计访问者visit模式
class NodeVisitor:
def visit(self, node):
methname = 'visit_' + type(node).__name__ #在访问者模式中 添加前缀重写属性名进行区分
meth = getattr(self, methname, None) #根据新属性名查找并返回属性值
if meth is None:
meth = self.generic_visit
return meth(node)
def generic_visit(self, node):
raise RuntimeError('No method'.format('visit_' + type(node).__name__))
class Evaluator(NodeVisitor):
def visit_Number(self, node):
return node.value
def visit_Add(self, node): #按照node树形结构 递归访问查找并返回属性值
return self.visit(node.left) + self.visit(node.right)
def visit_Sub(self, node):
return self.visit(node.left) - self.visit(node.right)
def visit_Mul(self, node):
return self.visit(node.left) * self.visit(node.right)
def visit_Div(self, node):
return self.visit(node.left) / self.visit(node.right)
def visit_Negate(self, node):
return -node.operand
上述方法本质为递归,对于较深的结构将不适用,避免递归的一个通常方法是使用一个栈或队列的数据结构。 例如,深度优先的遍历算法,第一次碰到一个节点时将其压入栈中,处理完后弹出栈。
class Evaluator(NodeVisitor):
def visit_Number(self, node):
return node.value
def visit_Add(self, node):
yield (yield node.left) + (yield node.right)
def visit_Sub(self, node):
yield (yield node.left) - (yield node.right)
def visit_Mul(self, node):
yield (yield node.left) * (yield node.right)
def visit_Div(self, node):
yield (yield node.left) / (yield node.right)
def visit_Negate(self, node):
yield - (yield node.operand)
它会将 node.left
返回给 visit()
方法,然后 visit()
方法调用那个节点相应的 visit_Name()
方法。 yield暂时将程序控制器让出给调用者,当执行完后,结果会赋值给value
16.让类支持比较操作
让某个类的实例支持标准的比较运算(比如>=,!=,<=,<等),但是又不想去实现那一大堆的特殊方法
使用python装饰器 functools.total_ordering
来装饰一个类,只需定义一个 __eq__()
方法, 外加其他方法(__lt__, __le__, __gt__, or __ge__)中的一个即可。 然后装饰器会自动为你填充其它比较方法。
以房屋面积大小比较为例,建立房屋基类,实例化房间
# Build a few houses, and add rooms to them
h1 = House('h1', 'Cape')
h1.add_room(Room('Master Bedroom', 14, 21))
h1.add_room(Room('Living Room', 18, 20))
h1.add_room(Room('Kitchen', 12, 16))
h1.add_room(Room('Office', 12, 12))
h2 = House('h2', 'Ranch')
h2.add_room(Room('Master Bedroom', 14, 21))
h2.add_room(Room('Living Room', 18, 20))
h2.add_room(Room('Kitchen', 12, 16))
h3 = House('h3', 'Split')
h3.add_room(Room('Master Bedroom', 14, 21))
h3.add_room(Room('Living Room', 18, 20))
h3.add_room(Room('Office', 12, 16))
h3.add_room(Room('Kitchen', 15, 17))
houses = [h1, h2, h3]
print('Is h1 bigger than h2?', h1 > h2) # prints True
print('Is h2 smaller than h3?', h2 < h3) # prints True
print('Is h2 greater than or equal to h1?', h2 >= h1) # Prints False
print('Which one is biggest?', max(houses)) # Prints 'h3: 1101-square-foot Split'
print('Which is smallest?', min(houses)) # Prints 'h2: 846-square-foot Ranch'
'''
('Is h1 bigger than h2?', True)
('Is h2 smaller than h3?', True)
('Is h2 greater than or equal to h1?', False)
'''
以上是关于Python类与对象的主要内容,如果未能解决你的问题,请参考以下文章