Python 常用语法整理 (二)

错误和异常

1. 错误

你经常会遇见的错误是语法错误,初学者掌握知识不扎实,经常会写出一些语法错误而不自知,一旦报错,就慌的一笔,下面是一个常见的错误语法示例

a = “python”
print(a)

如果你经常犯语法错误,那么要认真的复习一下基础语法了,语法并不是需要动脑子思考的知识,你只需记住它就好。

2. 异常

你的程序没有语法错误,但在运行期间仍然报错了,这种在运行期间发生的错误就是异常。

下面是几个比较典型的异常
示例1,ZeroDivisionError

def test(a, b):
    print(a/b)

if __name__ == '__main__':
    test(10, 5)
    test(10, 0)

2.1 异常在什么时候发生?

仔细观察上面的三个异常示例,你会发现一个有趣的现象,异常并不总是发生,每一个例子中,第一次调用函数的时候,函数是可以正确执行的,但是第二次调用时,由于各种各样的原因,都发生了错误。

和语法错误不同,在程序解析阶段,异常不会发生,因为此时,程序还没有正式运行,而在运行时,也不一定就会报错,只有遇到特定情况时才会发生错误,所以,才叫它异常,大部分情况下,程序都是可以正常执行的,偶尔,由于某个特殊原因导致程序发生了错误。

一个程序员,在写程序的时候,要考虑很多,基于过往的经验,他会刻意的处理一些边界情况或者特殊情况,以避免发生错误。

异常类继承关系

对于错误和异常,不要过于担心,他们并不是无穷无尽的,python的异常类是有限的,发生错误时耐心分析,认真总结,错误就会越来越少

BaseException
 +-- SystemExit
 +-- KeyboardInterrupt
 +-- GeneratorExit
 +-- Exception
      +-- StopIteration
      +-- StopAsyncIteration
      +-- ArithmeticError
       |     +-- FloatingPointError
       |     +-- OverflowError
       |     +-- ZeroDivisionError
      +-- AssertionError
      +-- AttributeError
      +-- BufferError
      +-- EOFError
      +-- ImportError
           +-- ModuleNotFoundError
      +-- LookupError
       |     +-- IndexError
       |     +-- KeyError
      +-- MemoryError
      +-- NameError
       |     +-- UnboundLocalError
      +-- OSError
       |     +-- BlockingIOError
       |     +-- ChildProcessError
       |     +-- ConnectionError
       |      |     +-- BrokenPipeError
       |      |     +-- ConnectionAbortedError
       |      |     +-- ConnectionRefusedError
       |      |     +-- ConnectionResetError
       |     +-- FileExistsError
       |     +-- FileNotFoundError
       |     +-- InterruptedError
       |     +-- IsADirectoryError
       |     +-- NotADirectoryError
       |     +-- PermissionError
       |     +-- ProcessLookupError
       |     +-- TimeoutError
      +-- ReferenceError
      +-- RuntimeError
       |     +-- NotImplementedError
       |     +-- RecursionError
      +-- SyntaxError
       |     +-- IndentationError
       |          +-- TabError
      +-- SystemError
      +-- TypeError
      +-- ValueError
       |     +-- UnicodeError
       |          +-- UnicodeDecodeError
       |          +-- UnicodeEncodeError
       |          +-- UnicodeTranslateError
      +-- Warning
           +-- DeprecationWarning
           +-- PendingDeprecationWarning
           +-- RuntimeWarning
           +-- SyntaxWarning
           +-- UserWarning
           +-- FutureWarning
           +-- ImportWarning
           +-- UnicodeWarning
           +-- BytesWarning
           +-- ResourceWarning

主动抛异常

1. 抛异常

有时,程序需要主动抛出异常,因为某些情况下,你需要反馈消息给更上层的调用者,告诉它有一些异常情况发生,而你抛出异常的地方,没有能力处理它,因此需要向上抛出异常。

这种情况为什么不让系统自己抛出异常呢?一个原因是上层的调用者本身就希望能够捕获有别于系统异常的自定义异常,二来,有些情况下,程序的逻辑是没有异常的,但是,从业务角度考虑,的确是一个不寻常的情况,因此需要我们主动抛出异常。

下面是抛出异常的一个例子

def divide(x, y):
    if y == 0:
        raise ZeroDivisionError("0不能做分母")
    return x/y

if __name__ == '__main__':
    divide(10, 5)
    divide(10, 0)

抛出异常时,你可以指定抛出哪个异常,如果你不想指定,那么可以抛出异常Exception, 它是所有异常的父类

def divide(x, y):
    if y == 0:
        raise Exception("0不能做分母")
    return x/y

if __name__ == '__main__':
    divide(10, 5)
    divide(10, 0)

2. 自定义异常类

在程序里引入自定义的异常类,可以让代码更具可读性,同时对异常的划分更加精细,那么在处理异常时也就更加具有针对性,自定义异常继承自Exception,或者那些类本身就继承自Exception

import requests

class HttpCodeException(Exception):
    pass

def get_html(url, headers):
    res = requests.get(url, headers=headers)
    print(res.status_code)
    if res.status_code != 200:
        raise HttpCodeException

    return res.text

try:
    text = get_html("http://www.coolpython.net", {})
    print(text)
except HttpCodeException:
    print("状态码不是200")

实践

# -*- coding: utf-8 -*-
# @Time    : 2023/4/10 16:05
# @Author  : Andy
# @File    : no_login_exception.py

class NotLoginException(Exception):
    """
    未登录异常
    Exception raised when no login
    """

    def __init__(self, msg):
        # 自定义异常类型的初始化
        self.msg = msg

    def __str__(self):
        # 自定义异常类型的string表达形式
        return "{}".format(repr(self.msg))

异常使用实践:

import requests

class HttpCodeException(Exception):

    def __init__(self, msg):
        self.msg = msg

    def __str__(self):
        return "{}".format(repr(self.msg))

def get_html(url, headers):
    res = requests.get(url, headers=headers)
    print(res.status_code)
    if res.status_code != 200:
        raise HttpCodeException("连接错误,抛出异常的提示")

    return res.text

if __name__ == '__main__':

    try:
        # 请求一个不存在的页面
        text = get_html("http://www.coolpython.net/page2=1", {})
        print(text)
    except HttpCodeException as e:
        print(e.msg)
        print("捕获异常,进行处理")

结果打印:

/envs/pythonStudy/bin/python /Code/pythonStudy/http_code_exception.py
404
连接错误,抛出异常的提示
捕获异常,进行处理

Process finished with exit code 0

或者是抛出异常,进行统一处理:

import requests

class HttpCodeOtherException(Exception):

    def __init__(self, msg):
        self.msg = msg

    def __str__(self):
        return "{}".format(repr(self.msg))

if __name__ == '__main__':

    try:
        # 请求一个不存在的页面
        url = "http://www.coolpython.net/page2=1"
        headers = {}
        res = requests.get(url, headers=headers)
        print(res.status_code)

        if res.status_code != 200:
            raise HttpCodeOtherException("连接错误,抛出异常的提示")

    except HttpCodeOtherException as e:
        print(e.msg)
        print("捕获异常,进行处理")

打印:

404
连接错误,抛出异常的提示
捕获异常,进行处理

异常信息收集

traceback模块可以准确获得有关异常的信息,这在稍大点的项目里非常有用。

在大一点的项目里,需要记录程序运行的日志,那些异常日志非常非常重要,将有助于你发现系统的问题。

import traceback
def divide(x, y):
    try:
        return x/y
    except:
        msg = traceback.format_exc()
        print(msg)

if __name__ == '__main__':
    #print(divide(10, 5))
    print(divide(10, 0))

traceback.format_exc() 可以获取有关异常的详细信息,这些信息就是平时你的程序遇到bug时报出来的那些异常信息

Traceback (most recent call last):
  File "/Users/kwsy/PycharmProjects/pythonclass/mytest/test.py", line 4, in divide
    return x/y
ZeroDivisionError: division by zero

有了这些信息,你就清楚的知道错误发生在哪里,异常的类型是什么,你甚至可以在日志里将函数的入参写入到日志中,这样,更加有助于你解决问题。

测试:

import traceback

import requests

class HttpCodeOtherException(Exception):

    def __init__(self, msg):
        self.msg = msg

    def __str__(self):
        return "{}".format(repr(self.msg))

if __name__ == '__main__':

    try:
        # 请求一个不存在的页面
        url = "http://www.coolpython.net/page2=1"
        headers = {}
        res = requests.get(url, headers=headers)
        print(res.status_code)

        if res.status_code != 200:
            raise HttpCodeOtherException("连接错误,抛出异常的提示")

    except HttpCodeOtherException as e:
        print(e.msg)
        print(" <<<<< 捕获异常,进行处理  >>>> ")
        # 异常捕获收集
        catch_err = traceback.format_exc()
        print(catch_err)

打印结果:

404
连接错误,抛出异常的提示
 <<<<< 捕获异常,进行处理  >>>> 
Traceback (most recent call last):
  File "/Users/kaiyi/Work/develop/Code/pythonStudy/http_code_exception_other.py", line 23, in <module>
    raise HttpCodeOtherException("连接错误,抛出异常的提示")
HttpCodeOtherException: '连接错误,抛出异常的提示'

Process finished with exit code 0

call()方法

本节再介绍 Python 类中一个非常特殊的实例方法,即 __call__() 。该方法的功能类似于在类中重载 () 运算符,使得类实例对象可以像调用普通函数那样,以“对象名()”的形式使用。

class CLanguage:
    # 定义__call__方法
    def __call__(self,name,add):
        print("调用__call__()方法",name,add)
clangs = CLanguage()
clangs("C语言中文网","http://c.biancheng.net")

执行结果:

调用__call__()方法 C语言中文网 http://c.biancheng.net

可以看到,通过在 CLanguage 类中实现 __call__() 方法,使的 clangs 实例对象变为了可调用对象

Python 中,凡是可以将 () 直接应用到自身并执行,都称为可调用对象。可调用对象包括自定义的函数、Python 内置函数以及本节所讲的类实例对象。

对于可调用对象,实际上“名称()”可以理解为是“名称.call()”的简写。仍以上面程序中定义的 clangs 实例对象为例,其最后一行代码还可以改写为如下形式:

clangs.__call__("C语言中文网","http://c.biancheng.net")

运行程序会发现,其运行结果和之前完全相同。

这里再举一个自定义函数的例子,例如:

def say():
    print("Python教程:http://c.biancheng.net/python")
say()
say.__call__()

程序执行结果为:

Python教程:http://c.biancheng.net/python
Python教程:http://c.biancheng.net/python

不仅如此,类中的实例方法也有以上 2 种调用方式,这里不再举例,有兴趣的读者可自行编写代码尝试。

call() 弥补 hasattr() 函数的短板

前面章节介绍了 hasattr() 函数的用法,该函数的功能是查找类的实例对象中是否包含指定名称的属性或者方法,但该函数有一个缺陷,即它无法判断该指定的名称,到底是类属性还是类方法。

要解决这个问题,我们可以借助可调用对象的概念。要知道,类实例对象包含的方法,其实也属于可调用对象,但类属性却不是。举个例子:

class CLanguage:
    def __init__ (self):
        self.name = "C语言中文网"
        self.add = "http://c.biancheng.net"
    def say(self):
        print("我正在学Python")
clangs = CLanguage()
if hasattr(clangs,"name"):
    print(hasattr(clangs.name,"__call__"))
print("**********")
if hasattr(clangs,"say"):
    print(hasattr(clangs.say,"__call__"))

程序执行结果为:

False
**********
True

可以看到,由于 name 是类属性,它没有以 call 为名的 call() 方法;而 say 是类方法,它是可调用对象,因此它有 call() 方法。

设计模式

创建模式

创建模式-单例模式

行为模式

责任链模式

Django高阶学习

C语言中文网 | Django框架教程(非常详细)

Django中Q()对象使用

一般我们在Django程序中查询数据库操作都是在QuerySet里进行进行,例如下面代码:

ret = ApplyProject.objects.filter(examine_status = 3)

如果要在这里使用条件语句,需要使用Q()对象

ret = ApplyProject.objects.filter(Q(examine_status = 2)|Q(examine_status = 3))#在filter中写条件语句用Q,并引用

Django切片分页

Django 框架如何不查表全表进行分页,请给出代码示例
在 Django 框架中,如果你想要实现分页功能但是不想查询整个表,可以使用 Paginator 类配合 QuerySet 的切片操作。下面是一个代码示例:

首先,假设你有一个名为 MyModel 的模型,你可以使用以下方法实现分页:

from django.core.paginator import Paginator
from myapp.models import MyModel

def get_paginated_data(page, items_per_page):
    # 使用 QuerySet 的切片操作来限制查询范围,提高效率
    start = (page - 1) * items_per_page
    end = page * items_per_page
    queryset = MyModel.objects.all()[start:end]

    # 使用 Paginator 进行分页
    paginator = Paginator(queryset, items_per_page)
    paginated_data = paginator.get_page(page)

    return paginated_data

然后,你可以在视图中调用这个函数:

from django.shortcuts import render
from .utils import get_paginated_data

def my_view(request):
    page = request.GET.get('page', 1)
    items_per_page = 10

    paginated_data = get_paginated_data(int(page), items_per_page)

    return render(request, 'myapp/my_template.html', {'paginated_data': paginated_data})

最后,在你的模板中,你可以使用以下代码显示分页数据:

{% for item in paginated_data %}
    <div>
        {{ item.field1 }} - {{ item.field2 }}
    </div>
{% endfor %}

<div class="pagination">
    <span class="step-links">
        {% if paginated_data.has_previous %}
            <a href="?page=1">&laquo; first</a>
            <a href="?page={{ paginated_data.previous_page_number }}">previous</a>
        {% endif %}

        <span class="current">
            Page {{ paginated_data.number }} of {{ paginated_data.paginator.num_pages }}.
        </span>

        {% if paginated_data.has_next %}
            <a href="?page={{ paginated_data.next_page_number }}">next</a>
            <a href="?page={{ paginated_data.paginator.num_pages }}">last &raquo;</a>
        {% endif %}
    </span>
</div>

这个示例中的 get_paginated_data 函数会根据指定的页面和每页显示的数据量进行查询。这样可以避免查询整个表,提高查询效率。

封装后的工具方法:

from django.core.paginator import Paginator

def get_paginated_data(queryset, page, items_per_page):
    # 使用 QuerySet 的切片操作来限制查询范围,提高效率
    start = (page - 1) * items_per_page
    end = page * items_per_page
    sliced_queryset = queryset[start:end]

    # 使用 Paginator 进行分页
    paginator = Paginator(sliced_queryset, items_per_page)
    paginated_data = paginator.get_page(page)

    return paginated_data

调用:

from django.shortcuts import render
from myapp.models import MyModel
from .utils import get_paginated_data

def my_view(request):
    queryset = MyModel.objects.all()
    page = request.GET.get('page', 1)
    items_per_page = 10

    paginated_data = get_paginated_data(queryset, int(page), items_per_page)

    return render(request, 'myapp/my_template.html', {'paginated_data': paginated_data})

相关文章:
酷Python|错误和异常处理
C语言中文网 | Django框架教程(非常详细)

为者常成,行者常至