Python:类与对象深度问题与解决技巧

引言
如何派生内置不可变类型并修改其实例化行为
我们想自定义一种新类型的元组,对于传入的可迭代对象,我们只保留其中int类型且值大于0的元素
实现的结果为:IntTuple([2,-2,‘xj’,[‘x’,‘y’],4]) => (2,4)
如何继承内置tuple 实现IntTuple

# 继承元组类型
class IntTuple(tuple):
    # iterable 是什么,是一个可迭代的对象
    def __init__(self, iterable):
        # 现在是列表的形式
        for i in iterable:
            if isinstance(i,int) and i>0:
                super().__init__(i)
                print(i)

intTuple = IntTuple([2, -2, 'xj', ['x', 'y'], 4])

运行结果:

TypeError: object.__init__() takes no parameters

报错了,为什么?因为:如下
在这里插入图片描述

self类对象到底是谁创建的呢?

class A(object):
    def __new__(cls, *args, **kwargs):
        print('A.__new__', cls, args)
        return object.__new__(cls)

  
    def __init__(self, a, b):
        print('A.__init__', a, b)


a = A(1, 2)

运行结果:

A.__new__ <class '__main__.A'> (1, 2)
A.__init__ 1 2

分析:当创建__new__这个方法的时候,在创建__init__方法的时候他的执行顺序是先执行__new__这个方法,如果把__new__这个方法中的return这个方法注释掉之后会怎么样

class A(object):
    def __new__(cls, *args, **kwargs):
        print('A.__new__', cls, args)
        # return object.__new__(cls)   为什么要使用return这个返回呢,如果不返回,那__init__就不能被调用

    # 当注释了__new__中的return语句的时候,并不会执行__init__方法呢,那是因为其实真正创建的方法其实是__new__,而不是__init__
    # __init__其实是真正初始化的方法.真正创建对象出来是__new__这个方法
    def __init__(self, a, b):
        print('A.__init__', a, b)


a = A(1, 2) 

运行结果:

A.__new__ <class '__main__.A'> (1, 2)

分析结果:它只执行了__new__中的这个方法,它并不会去执行这个__init__这个方法,这个是为什么呢,那是因为其实__init__只是个初始化的方法,它真正创建对象的方法不是__init__,而是__new__这个方法

元组重写__new__方法

class IntPuter(tuple):
    def __new__(cls,iterable):
        f = (i for i in iterable if isinstance(i,int) and i>0)  # 返回的结果是生成器
        return super().__new__(cls,f)

a = [2, -2, 'jr', ['x', 'y'], 4]
da = IntPuter(a)
print(da)

运行结果:

(2, 4)

2.如何为创建大量实例节省内存
在游戏中,定义了玩家类player,每有一个在线玩家,在服务器内则有一个player的实例,当在线人数很多时,将产生大量实例(百万级)
如何降低这些大量实例的内存开销?
解决方案:
定义类的__slots__属性,声明实例有哪些属性(关闭动态绑定)

扫描二维码关注公众号,回复: 9656199 查看本文章

class Person(object):
    __slots__ = ['name', 'age', 'sex','start']

    def __init__(self, name, age, sex,start=1):
        self.name = name
        self.age = age
        self.sex = sex
        self.start = start


class Date(object):
    def __init__(self, name, age, sex,start=1):
        self.name = name
        self.age = age
        self.sex = sex
        self.start = start


a = Person('钢铁侠', 18, '男')
a1 = Date('钢铁侠', 18, '男')

print(dir(a))
print(dir(a1))
try:
    print(a.__dict__)
except Exception as e:    # 尝试输出__dict__属性,发现没有,就无法动态的装配类属性,达到省内存的目的
    print(e)
print(a1.__dict__)

运行结果:

['__class__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__slots__', '__str__', '__subclasshook__', 'age', 'name', 'sex', 'start']
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'age', 'name', 'sex', 'start']
'Person' object has no attribute '__dict__'
{'name': '钢铁侠', 'age': 18, 'sex': '男', 'start': 1}

with语句

  • 万能的报错器Exception
  • with语句不仅能支持上下文管理协议对象使用。支持本协议的对象
  • 当with语句执行时,便执行上下文表达式(context_expr)(一般为某个方法)来获取一个上下文管理起对象,上下文管理器的职责是一个上下文对象,用于在with语句中处理细节
  • 一旦获得了上下文对象,就会调用__enter__()方法,将完成with语句块执行前的所有准备工作,如果with语句后面跟了as语句,则用__enter__()方法的返回值来赋值;
  • 当with语句块结束时,无论是正常结束,还是由于异常,都会调用上下文对象的__exit__()方法,exit()方法有3个参数,如果with语句正常结束,三个参数全部是None;如果发生异常,三个参数的值分别是等于调用sys.exc_info()函数返回的三个值:类型(异常类)、值(异常实例)和跟踪记录(traceback),相对应的跟踪记录对象
  • 因为上下问管理器主要作用于共享资源,enter()和__exit__()方法基本是完成的是分配和释放资源的底次工作。

当自定义类使用with来管理

class Sample(object):

    def speak(self):
        print('this is speak')


with Sample() as sample:
    sample.speak()

运行结果:

  with Sample() as sample:
AttributeError: __enter__

将会抛出个__enter__这个异常信息,为什么会这样,因为由于自定义类使用with管理上下文的时候,一旦获得上下文对象都会调用__enter__这个方法,将完成with语句执行前的所有工作,如果with语句后面跟了as语句则用__enter__()方法来赋值

class Sample(object):
    def __enter__(self):
        print('开始执行start')
        return self

    def speak(self):
        print('this is speak')


with Sample() as sample:
    sample.speak()

运行结果:

    with Sample() as sample:
AttributeError: __exit__

当创建出__exit__时,也抛出了异常信息,这是为什么呢?当with语句块结束时,无论是正常结束,还是由于异常,都会调用上下文对象的__exit__()方法,exit()方法有3个参数,如果with语句正常结束,三个参数全部是None;如果发生异常,三个参数的值分别是等于调用sys.exc_info()函数返回的三个值:类型(异常类)、值(异常实例)和跟踪记录(traceback),相对应的跟踪记录对象

class Sample(object):
     # 获取资源
    def __enter__(self):
        print('开始执行start')
        return self
    # 释放资源
    def __exit__(self, exc_type, exc_val, exc_tb):     # exc_type异常类捕获,exc_val异常值捕获,exc_tb错误信息追踪
        print('执行结束')
        return self

    def speak(self):
        print('this is speak')


with Sample() as sample:
    sample.speak()

运行结果:

开始执行start
this is speak
执行结束

仔细看执行的顺序,无论代码的顺序是怎么修改,它的代码执行顺序是:
第一步:先执行__enter__
第二步:自己定义的方法
第三步:最后执行__exit__

contextlib简化上下文管理器

import contextlib

@contextlib.contextmanager
def write(filename):
    print('open file.txt')
    yield {}  # yield的上面相当于是__exit__
    #   yield的下面相当与是__enter__
    print('close file.txt')


with write('demo.txt')as f:
    print('file  正在读取文件')

运行结果:

open file.txt
file  正在读取文件
close file.txt

使用contextlib可以更加简易的实现上下文管理文件操作
如何创建可管理的对象属性

在面向对象编程中,我们把方法看做对象的接口。直接访问对象的属性可能是不安全的,或设计上不够灵活,但是使用调用方法在形式上不如访问属性简洁。

class C(object):
    def __init__(self,age):
        self.age = age

    @property
    def x(self):
        return self.age > 0

    @x.setter
    def x(self,x):
        if x > 0:
            print('我的年龄是{}'.format(x))
        else:
            raise Exception

a = C(18)
a.x = 20   # 通过属性的方式来创建值
# print(a.x)

运行结果:

我的年龄是20

如何让类支持比较级运算
有时我们希望自定义类的实例间可以使用<, <=, >, >=, ==, !=符号进行比较,我们自定义比较的行为。
比如,有一个矩形,比较两个矩形的实例时,我们希望比较的是面积。
比如,有一个矩形和一个圆形,我们希望比较一个矩形实例和一个圆形实例,
我们希望它们比较的是面积。
在这里插入图片描述
__lt__:比较的是小于
__ge__:比较的是大于
__eq__:比较的是等于

class Rect(object):
    def __init__(self, w, h):
        self.w = w
        self.h = h

    def __eq__(self, other):  # __eq__这个方法是可以判断等于等于,但是并不能判断 >= 或 <=
        return self.area() == other.area()

        # self其代表着本身

    def __lt__(self, other):  # othre是什么,是你要跟我比较的字符
        return self.area() < other.area()  # __lt__这个方法比较的是小于但它能比较大于嘛??
        # 答案是可以的,在Python内部,它会自定义做了这么一个判断

    def area(self):
        return self.w * self.h

    def __str__(self):  # __str__这个方法是什么时候才调用这个方法 是在print()打印的时候调用
        return '%s %s' % (self.w, self.h)


a = Rect(4, 4)
b = Rect(3, 4)
print(a == b)
print(a < b)
print(a != b)
print(a > b)

运行结果:

False
False
True
True

但大家会却不能比较(大于等于)或(小于等于)

class Rect(object):
    def __init__(self, w, h):
        self.w = w
        self.h = h

    def __eq__(self, other):  # __eq__这个方法是可以判断等于等于,但是并不能判断 >= 或 <=
        return self.area() == other.area()

        # self其代表着本身

    def __lt__(self, other):  # othre是什么,是你要跟我比较的字符
        return self.area() < other.area()  # __lt__这个方法比较的是小于但它能比较大于嘛??
        # 答案是可以的,在Python内部,它会自定义做了这么一个判断

    def area(self):
        return self.w * self.h

    def __str__(self):  # __str__这个方法是什么时候才调用这个方法 是在print()打印的时候调用
        return '%s %s' % (self.w, self.h)


a = Rect(4, 4)
b = Rect(3, 4)
print(a == b)
print(a <= b)
print(a != b)
print(a >= b)

运行结果:

    print(a <= b)
TypeError: '<=' not supported between instances of 'Rect' and 'Rect'
# 还是会抛出异常信息

Python中有这么一个库:functools 这个库中有total_ordering这个方法可以实现上面的>=,<=的过程

from functools import total_ordering
@total_ordering
class Rect(object):
    def __init__(self, w, h):
        self.w = w
        self.h = h

    def __eq__(self, other):  # __eq__这个方法是可以判断等于等于,但是并不能判断 >= 或 <=
        return self.area() == other.area()

        # self其代表着本身

    def __lt__(self, other):  # othre是什么,是你要跟我比较的字符
        return self.area() < other.area()  # __lt__这个方法比较的是小于但它能比较大于嘛??
        # 答案是可以的,在Python内部,它会自定义做了这么一个判断

    def area(self):
        return self.w * self.h

    def __str__(self):  # __str__这个方法是什么时候才调用这个方法 是在print()打印的时候调用
        return '%s %s' % (self.w, self.h)


a = Rect(4, 4)
b = Rect(3, 4)
print(a == b)
print(a <= b)
print(a != b)
print(a >= b)

运行结果:

False
False
True
True

类进行比较

from functools import total_ordering
import math
import abc
@total_ordering
class Rect(object):
    def __init__(self, w, h):
        self.w = w
        self.h = h

    def __eq__(self, other):  # __eq__这个方法是可以判断等于等于,但是并不能判断 >= 或 <=
        return self.area() == other.area()

        # self其代表着本身

    def __lt__(self, other):  # othre是什么,是你要跟我比较的字符
        return self.area() < other.area()  # __lt__这个方法比较的是小于但它能比较大于嘛??
        # 答案是可以的,在Python内部,它会自定义做了这么一个判断

    def area(self):
        return self.w * self.h

    def __str__(self):  # __str__这个方法是什么时候才调用这个方法 是在print()打印的时候调用
        return '%s %s' % (self.w, self.h)


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

class Circular(object):
    def __init__(self,r):
        self.r = r

    def area(self):
        return self.r ** 2 * math.pi


    def __eq__(self, other):  # __eq__这个方法是可以判断等于等于,但是并不能判断 >= 或 <=
        return self.area() == other.area()

        # self其代表着本身

    def __lt__(self, other):  # othre是什么,是你要跟我比较的字符
        return self.area() < other.area()  # __lt__这个方法比较的是小于但它能比较大于嘛??
        # 答案是可以的,在Python内部,它会自定义做了这么一个判断

r = Circular(5)
print(r >= a)

运行结果:

True

但你会发现类比较代码重用性很多,我们可以通过继承自抽象基类去完成这代码的操作
优化后的代码:


from functools import total_ordering
import math
import abc


@total_ordering
class fun(metaclass=abc.ABCMeta):  #定义一个抽象基类
    def area(self):
        pass

    def __eq__(self, other):
        return self.area() == other.area()

    def __lt__(self, other):
        return self.area() < other.area()


@total_ordering
class Rect(fun):   # 继承自抽象基类
    def __init__(self, w, h):
        self.w = w
        self.h = h

    def area(self):
        return self.w * self.h


a = Rect(4, 4)


class Circular(fun):
    def __init__(self, r):
        self.r = r

    def area(self):
        return self.r ** 2 * math.pi


r = Circular(5)
print(r >= a)

运行结果:

True

通过实例方法调用字符串
他们都有一个获取图形面积的方法,但是方法名字不同,我们可以实现一个统一的获取面积的函数,使用每种方法名进行尝试,调用相应类的接口

三角形

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

    def get_area(self):
        a, b, c = self.a, self.b, self.c
        p = (a + b + c) / 2
        return (p * (p - a) * (p - b) * (p - c)) ** 0.5

矩形

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

    def getArea(self):
        return self.a * self.b

class Circle:
    def __init__(self, r):
        self.r = r

    def area(self):
        return self.r ** 2 * 3.14159

getatter
语法:getattr(object, name[, default])
object:对象
name:字符串,对象属性
default:默认返回值,在没有对应的属性将会触发AttributeError

map函数
语法:map(function, iterable, …)
会根据提供的函数对指定序列做映射。
第一个参数 function 以参数序列中的每一个元素调用 function 函数,返回包含每次 function 函数返回值的新列表。
function:函数
iterable:一个或多个序列

通过实例方法调用

from lib1 import Triangle
from lib2 import Rectangle
from lib3 import Circle

shap1 = Triangle(3, 4, 5)
shap2 = Rectangle(4, 6)
shap3 = Circle(1)


def demo(shape):
    list_data = ['get_area', 'getArea', 'area']
    for name in list_data:
        f = getattr(shape, name, None)
        if f:
            return f()


list_demo = [shap1, shap2, shap3]
list_data = list(map(demo, list_demo))
print(list_data)

运行结果:

[6.0, 24, 3.14159]
发布了54 篇原创文章 · 获赞 26 · 访问量 6177

猜你喜欢

转载自blog.csdn.net/qq_37662827/article/details/103739667