• Python语法基础-函数,类以及调试处理


    1. 函数的定义

    python中函数有两种:

    • python自带的函数
    • 用户定义函数

    返回多个值

    原来返回值是一个tuple!但是,在语法上,返回一个tuple可以省略括号,而多个变量可以同时接收一个tuple,按位置赋给对应的值,所以,Python的函数返回多值其实就是返回一个tuple,但写起来更方便

    1.1函数的参数

    参数 含义 输入
    位置参数 def power(x,n) 实际参数
    默认参数 def power(x,n=2) 实际+默认参数(需要改变时)
    可变参数 def power(*args) 传入任意个参数,内部组装成tuple
    关键字参数 def person(name, age, **kw) 传入带参数名的参数,组装成dict
    命名关键字参数 def person(name,age,*, city, job) 限制关键字参数的名字(必须传入参数名)
    • 顺序: 必选参数<---默认参数<---可变参数<---命名关键字参数<---关键字参数
    # 关键字参数
    def person(name, age, **kw):
        print('name:', name, 'age:', age, 'other:', kw)  
    
    person('hao', 20) # name: Michael age: 30 other: {}
    person('hao', 20, gener = 'M', job = 'Engineer') # name: Adam age: 45 other: {'gender': 'M', 'job': 'Engineer'}  
    extra = {'city': 'Beijing', 'job': 'Engineer'}
    person('Jack', 24, **extra)  
    
    # 命名关键字参数
    def person(name, age, *, city='Beijing', job):
        print(name, age, city, job)
    
    person('Jack', 24, job = '123')
    person('Jack', 24, city = 'Beijing', job = 'Engineer')
    
    # Combination
    # 可变 + 关键字参数
    def f1(a, b, c=0, *args, **kw):
        print('a =', a, 'b =', b, 'c =', c, 'args =', args, 'kw =', kw)
    
    f1(1, 2, 3, 'a', 'b')   # a = 1 b = 2 c = 3 args = ('a', 'b') kw = {'x': 99}
    f1(1, 2, 3, 'a', 'b', x=99) # a = 1 b = 2 c = 0 d = 99 kw = {'ext': None}
    
    # 默认参数 + 命名关键字参数 + 关键字参数
    def f2(a, b, c=0, *, d, **kw):
        print('a =', a, 'b =', b, 'c =', c, 'd =', d, 'kw =', kw)
    
    f2(1, 2, d=99, ext=None) # a = 1 b = 2 c = 0 d = 99 kw = {'ext': None}
    

    2. 面向对象编程

    • 面向过程: 根据业务逻辑从上到下写代码
    • 面向对象: 对数据与函数绑定到一起,进行封装,这样更快速的开发过程,减少代码重复使用

    数据封装、继承和多态是面向对象的三大特点

    2.1. 类(抽象概念)和对象(具体概念)

    玩具模具(类)-》 火车玩具,飞机玩具..(对象)

    类的组成结构

    • 类名:狗
    • 类的属性:一组数据(狗的颜色,性别...)
    • 类的方法: 运行进行操作的方法行为(行为,会叫,会咬人...)-> 用函数设计
    类的组成 特性 例子 例子
    类名 名称
    类的属性 一组数据 狗的颜色,性别 身高,年龄
    类的方法 运行进行操作的方法行为 行为,会叫,会咬人 跑,打架

    • 类是创建实例的模板,而实例则是一个一个具体的对象,各个实例拥有的数据都互相独立,互不影响;
    • 方法就是与实例绑定的函数,和普通函数不同,方法可以直接访问实例的数据;
    • 通过在实例上调用方法,我们就直接操作了对象内部的数据,但无需知道方法内部的实现细节。
    • 继承可以把父类的所有功能都直接拿过来,这样就不必重零做起,子类只需要新增自己特有的方法,也可以把父类不适合的方法覆盖重写。

    (1). 定义类

    # 定义类
    class Dog(object):
    
      # 定义初始化方法
      def __init__(self,weight,color):
        """
          self: python会把对象的地址自动传给self,不要自己传
          weight, color: 接收外部属性
        """
        # 定义属性
        self.weight = weight
        self.color = color
    
      # 魔法方法: 当只打印Dog对象的时候,就可以打印这里的东西
      def __str__(self):
        msg = "dog weight" + self.weight + "color" + self.color
        return "哈哈哈"
    
      def getweight(self):
        return self.weight
      def getcolor(self):
        return self.color
      def setweight(self):
        self.weight = 100
      def setcolor(self):
        self.color = "green"
      
      # 定义方法
      def bark(self):
        """
          self: python解释器默认把调用该class的对象地址给它
        """
        print("666")
        
      def run(self):
        print("777")
    
    
     # 创建对象
    huskey = Dog(5, 'Black')  # 创建一个哈士奇
    keji = Dog(10, 'Green')  
    
    huskey.bark()  # 哈士奇叫
    huskey.run()   # 哈士奇跑
    huskey.weight = 100  # 哈士奇属性
    huskey.color = 'green'
    
    
    • self表示自己,表示调用类的对象本身
    • python中类似 __***__的方法,是魔法方法,有特殊用途

    (2). 类的数据封装

    面向对象编程的一个重要特点就是数据封装,比如在上面例子中,Dog类的每个实例都有weight和color,我们可以直接在Dog类的内部定义访问数据的函数,这样,就把“数据”给封装起来了。这些封装数据的函数是和Dog类本身是关联起来的,我们称之为类的方法:

    class Dog(object):
    
        def __init__(self, weight, color):
            self.weight = weight
            self.color = color
    
        def print_dog(self):
            print('%s: %s' % (self.weight, self.color))
    

    我们从外部看Dog类,就只需要知道,创建实例需要给出weightcolor,而如何打印,都是在Dog类的内部定义的,这些数据和逻辑被“封装”起来了,调用很容易,但却不用知道内部实现的细节。

    (3). 访问限制

    • 从前面Dog类的定义来看,外部代码还是可以自由地修改一个实例的weightcolor属性,我们可以设置内部属性不被外部访问:
    
    class Dog(object):
        # 加下划线
        def __init__(self, weight, color):
            self.__weight = weight
            self.__color = color
    
        def print_dog(self):
            print('%s: %s' % (self.__weight, __self.color))
    
    huskey = Dog(60, 'green')
    print(huskey.__name)      # 无法再访问属性,会出错
    
    • 但是如果外部代码需要获取weightcolor属性呢?-> 添加get_weightget_color方法
    class Dog(object):
        ...
    
        def get_weight(self):
            return self.__weight
    
        def get_color(self):
            return self.__color
    
    • 如果又要允许外部代码修改weightcolor怎么办?->添加set_weightset_color方法
    class Dog(object):
        ...
    
        def set_weight(self,weight):
            self.__weight = weight
    
        def set_color(self):
            self.__color = color
    
    • 为什么要费这么大周折呢?之前不是可以直接husky.weight=10直接改吗
      因为在方法中,可以对参数做检查,避免传入无效的参数:
    class Dog(object):
        ...
    
        def set_weight(self,weight):
          if 0<=weight<=100:
            self.__weight = weight
          else:
            raise ValueError('Bad weight')
    

    (4). 获取对象信息

    # type():判断对象类型
    type([1,2,3])
    type('abc') == str
    type(huskey)
    
    # isinstance(): 一个对象是否属于某种类型  
    isinstance(h, husky)
    isinstance(h, cat)
    
    # dir() 获取一个对象的所有属性和方法 
    dir('ABC')
    dir('huskey')
    

    2.2. 继承和多态

    继承就是:父类和子类之间的交互关系

    (1)为什么要用继承?

    最大的好处是子类获得了父类的全部功能

    class Animal(object):
        def run(self):
            print('Animal is running...')
    
    # Dog继承父类Animal,并且自动拥有run属性,可以在Dog对象中直接调用
    class Dog(Animal):
        pass
    
    # 子类可以自己增加一些方法(eat),同时也可以重写父类的方法(run)---》这就是多态
    class Cat(Animal):
        def run(self):
          print('Dog is running...')
    
        def eat(self):
          print('Eating meat...')
    
    

    (2)什么是多态?

    在继承关系中,如果一个实例的数据类型是某个子类,那它的数据类型也可以被看做是父类。但是反过来就不行了

    C是dog,C也是animal
    D是animal,但你不能说D也是Dog

    # 首先创建对象并查看对象类型
    a = list() # a是list类型
    isinstance(a, list)   # true
    b = Animal() # b是Animal类型
    isinstance(b, Animal) # true
    c = Dog() # c是Dog类型
    isinstance(c, Dog)    # true
    
    
    # C既是Dog也是Animal
    isinstance(c, Animal)  # true  
    
    # 反过来就错了
    isinstance(b, Dog)      # false
    

    (3)那么为题来了。这样设置成多态有什么好处呢?

    • 任何依赖父类作为参数的函数或者方法都可以不加修改地正常运行,原因就在于多态。

    • 当我们需要传入DogCatTortoise……时,我们只需要接收Animal类型就可以了,因为Dog、Cat、Tortoise……都是Animal类型,然后,按照Animal类型进行操作即可。由于Animal类型有run()方法,因此,传入的任意类型,只要是Animal类或者子类,就会自动调用实际类型的run()方法,这就是多态的意思

    • 对于一个变量,我们只需要知道它是Animal类型,无需确切地知道它的子类型,就可以放心地调用run()方法,而具体调用的run()方法是作用在Animal、Dog、Cat还是Tortoise对象上,由运行时该对象的确切类型决定

    def run_twice(animal):
        animal.run()
    
    run_twice(Animal()) # 调用animal的run
    run_twice(Dog())    # 调用dog的run
    run_twice(Cat())    # 调用cat的run
    

    3. 面向对象高级编程


    (1). __slots__的使用

    动态绑定允许我们在程序运行的过程中动态给class加上功能, 但是,如果我们想要限制实例的属性怎么办?比如,只允许对Student实例添加nameage属性。

    class中定义一个特殊的__slots__变量,来限制该class实例能添加的属性:

    class Student(object):
        __slots__ = ('name', 'age') # 用tuple定义允许绑定的属性名称
    
     s = Student() # 创建新的实例
     s.name = 'Michael' # 绑定属性'name'
    
     s.score = 99 # 绑定属性'score'-->出错
    

    __slots__定义的属性仅对当前类实例起作用,对继承的子类是不起作用的

    (2). @property的使用

    在绑定属性时,如果我们直接把属性暴露出去,虽然写起来很简单,但是,没办法检查参数,导致可以把成绩随便改,解决方案:

    1. 通过set_score设置成绩,在通过get_score获取成绩,在set_score中检查参数(已经讲过)
    2. 使用内置的@property装饰器,既可以检查参数,又可以类似属性那样访问类的变量

    @property广泛应用在类的定义中,可以让调用者写出简短的代码,同时保证对参数进行必要的检查,这样,程序运行时就减少了出错的可能性。

    class Student(object):
    
        # @property将get_Score方法变成属性
        @property
        def score(self):
            return self._score
    
        # @score.setter将set_score方法变成属性赋值
        @score.setter
        def score(self, value):
            if not isinstance(value, int):
                raise ValueError('score must be an integer!')
            if value < 0 or value > 100:
                raise ValueError('score must between 0 ~ 100!')
            self._score = value
    
    s = Student()
    s.score = 60 # OK,实际转化为s.set_score(60)
    s.score # OK,实际转化为s.get_score()
    s.score = 9999   # error
    

    (3). 多重继承MixLn的设计

    MixIn的目的就是给一个类增加多个功能,这样,在设计类的时候,我们优先考虑通过多重继承来组合多个MixIn的功能,而不是设计多层次的复杂的继承关系

    class Animal(object):
        pass
    
    class Bird(Animal):
        pass
    
    class Parrot(Bird):
        pass
    
    # 此时我们需要加入额外的功能fly
    # 先定义好fly的类
    class Flyable(object):
        def fly(self):
            print('Flying...')
    
    # 同时继承两个类
    class Parrot(Bird, Flyable):
        pass
    

    (4). 定制个性化类

    1): __str____repr__

    让打印的object更漂亮:

    • __str__: 用print打印
    • __repr__: 直接输入对象名
    class Student(object):
        # 加下划线
        def __init__(self, name):
            self.__name = name
    
        def __str__(self):
          msg = 'Student name is' + self.__name
          return msg
    
        __repr__ = __str__
    
    s = Student()
    print(s)    # 打印出 Student name is.....
    
    s     # 效果跟上面的一样
    

    2): __iter__

    将类定义成类似list或者tuple那种,可以用于for循环的作用

    class Fib(object):
        def __init__(self):
            self.a, self.b = 0, 1 # 初始化两个计数器a,b
    
        def __iter__(self):
            return self # 实例本身就是迭代对象,故返回自己
    
        def __getitem__(self, n):
            if isinstance(n, int): # n是索引
                a, b = 1, 1
                for x in range(n):
                    a, b = b, a + b
                return a
            if isinstance(n, slice): # n是切片
                start = n.start
                stop = n.stop
                if start is None:
                    start = 0
                a, b = 1, 1
                L = []
                for x in range(stop):
                    if x >= start:
                        L.append(a)
                    a, b = b, a + b
                return L
            
        def __next__(self):
            self.a, self.b = self.b, self.a + self.b # 计算下一个值
            if self.a > 100000: # 退出循环的条件
                raise StopIteration()
            return self.a # 返回下一个值
    
    for i in Fib():
      print(n)          # 1,1,2,3,5,.......,75025
    
    # 因为__getitem__的作用,可以index某个值
    f = Fib()
    f[0]   # 1
    
    # 也可以切片
    print(f[0:5])   # 1,1,2,3,5
    

    3): __call__

    直接在实例本身上调用的一种构造方法

    class Student(object):
        def __init__(self, name):
            self.name = name
    
        def __call__(self):
            print('My name is %s.' % self.name)
    
    s = Student('Michael')
    s() # My name is Michael.
    
    • 实例进行直接调用就好比对一个函数进行调用一样,所以你完全可以把对象看成函数,把函数看成对象

    4) 枚举类Enum

    定义常量

    from enum import Enum, unique
    
    # unique保证没有重复值
    @unique
    class Weekday(Enum):
        Sun = 0 # Sun的value被设定为0
        Mon = 1
        Tue = 2
        Wed = 3
        Thu = 4
        Fri = 5
        Sat = 6
    
    # 访问枚举类 
    print(Weekday.Mon)         # Weekday.Mon
    print(Weekday.Mon.value)   # 1
    print(Weekday(1)))         # # Weekday.Mon
    
    

    5) type()动态创建类Class

    type()函数既可以返回一个对象的类型,又可以创建出新的类型

    type()和Class()的功能是一样的

    要创建一个class对象,type()函数依次传入3个参数:

    • class的名称;
    • 继承的父类集合,注意Python支持多重继承,如果只有一个父类,别忘了tuple的单元素写法;
    • class的方法名称与函数绑定,这里我们把函数fn绑定到方法名hello上。
    def fn(self, name='world'): # 先定义函数
      print("hello", name)
    
    Hello = type('Hello', (object,), dict(hello=fn)) # 创建Hello class
    

    4. 错误处理

    (4.1).try-catch->有错误后就结束了不执行以后的

    try:
        print('try...')
        r = 10 / int('a')
        print('result:', r)
    except ValueError as e:
        print('ValueError:', e)
    except ZeroDivisionError as e:
        print('ZeroDivisionError:', e)
    finally:
        print('finally...')
    print('END')
    
    
    try...
    except: division by string
    finally...
    END
    

    try来运行这段代码,如果执行出错,则后续代码不会继续执行,而是直接跳转至错误处理代码,即except语句块,执行完except后,如果有finally语句块,则执行finally语句块,至此,执行完毕。

    (4.2).logging->有错误了打印完信息后继续执行

    import logging
    
    def foo(s):
        return 10 / int(s)
    
    def bar(s):
        return foo(s) * 2
    
    def main():
        try:
            bar('0')
        except Exception as e:
            logging.exception(e)
    
    main()
    print('END')
    
    # 结果
    ERROR:root:division by zero
    Traceback (most recent call last):
      File "err_logging.py", line 13, in main
        bar('0')
      File "err_logging.py", line 9, in bar
        return foo(s) * 2
      File "err_logging.py", line 6, in foo
        return 10 / int(s)
    ZeroDivisionError: division by zero
    END  # 继续执行了
    
    • 通过配置,logging还可以把错误记录到日志文件里,方便事后排查

    (4.3).自定义抛出raise错误

    class FooError(ValueError):
        pass
    
    def foo(s):
        n = int(s)
        if n==0:
            raise FooError('invalid value: %s' % s)
        return 10 / n
    foo('0')
    
    # 结果
    Traceback (most recent call last):
      File "err_throw.py", line 11, in <module>
        foo('0')
      File "err_throw.py", line 8, in foo
        raise FooError('invalid value: %s' % s)
    __main__.FooError: invalid value: 0
    

    5. 调试

    方法 优点 缺点
    print方法 简单使用 重复多
    assert 简单使用 重复多
    logging 不会抛出错误,可以输入文档 重复多
    pdb python内置调试器 重复多
    VS Code 强强强

    (5.1).print方法(略过)

    (5.2).断言assert

    凡是用print()来辅助查看的地方,都可以用断言(assert)来替代:

    def foo(s):
        n = int(s)
        assert n != 0, 'n is zero!'
        return 10 / n
    
    def main():
        foo('0')
    
    # 结果
    Traceback (most recent call last):
      ...
    AssertionError: n is zero!
    

    assert的意思是,表达式n != 0应该是True,否则,根据程序运行的逻辑,后面的代码肯定会出错。如果断言失败,assert语句本身就会抛出AssertionError

    (5.3).logging

    print()替换为logging是第3种方式,和assert比,logging不会抛出错误,而且可以输出到文件:

    import logging
    logging.basicConfig(level=logging.INFO)
    
    s = '0'
    n = int(s)
    logging.info('n = %d' % n)
    print(10 / n)
    
    # 结果
    INFO:root:n = 0
    Traceback (most recent call last):
      File "err.py", line 8, in <module>
        print(10 / n)
    ZeroDivisionError: division by zero
    

    logging可以指定不同信息的级别

    • debug
    • info
    • warning
    • error

    (5.4).pdb.set_trace()

    我们只需要import pdb,然后,在可能出错的地方放一个pdb.set_trace(),就可以设置一个断点

    import pdb
    
    s = '0'
    n = int(s)
    # 暂停并进入pdb调试环境
    pdb.set_trace() # 运行到这里会自动暂停
    print(10 / n)
    

    (5.5). IDE

    直接在代码中设置断掉调试

    graph TD; A-->B; A-->C; B-->D; C-->D;
  • 相关阅读:
    在servlet中实现页面跳转
    我的jsp学习日记——001:@include(静态包含指令)和jsp:include(动态包含指令)的区别
    myEclipse中修改新建jsp文档的编码格式
    js判断一个图片是否已经存在于缓存中
    MyEclipse的JavaScript提示插件(JSEclipse)
    pku2051 Argus
    pku2084 Game of Connections
    pku2001 Shortest Prefixes
    pku2007 Scrambled Polygon
    pku2153 Rank List
  • 原文地址:https://www.cnblogs.com/haochen273/p/10254834.html
Copyright © 2020-2023  润新知