美文网首页
Python基础之函数,面向对象

Python基础之函数,面向对象

作者: 上善若泪 | 来源:发表于2022-11-02 16:45 被阅读0次

    1 函数

    函数是组织好的,可重复使用的,用来实现单一,或相关联功能的代码段。
    函数能提高应用的模块性,和代码的重复利用率。

    1.1 定义一个函数

    可以定义一个由自己想要功能的函数,以下是简单的规则:

    • 函数代码块以 def 关键词开头,后接函数标识符名称和圆括号 ()
    • 任何传入参数和自变量必须放在圆括号中间,圆括号之间可以用于定义参数。
    • 函数的第一行语句可以选择性地使用文档字符串,用于存放函数说明。
    • 函数内容以冒号 :起始,并且缩进。
    • return [表达式]结束函数,选择性地返回一个值给调用方,不带表达式的 return 相当于返回 None

    语法
    Python 定义函数使用 def 关键字,一般格式如下:

    def 函数名(参数列表):
        函数体
    

    默认情况下,参数值和参数名称是按函数声明中定义的顺序匹配起来的。
    例子如下:

    #!/usr/bin/python3
     # 计算面积函数
    def area(width, height):
        return width * height
     
    def print_welcome(name):
        print("Welcome", name)
     
    print_welcome("Test")
    w = 4
    h = 5
    print("width =", w, " height =", h, " area =", area(w, h))
    
    结果:
    Welcome Test
    width = 4  height = 5  area = 20
    

    1.2 函数调用

    定义一个函数:给了函数一个名称,指定了函数里包含的参数,和代码块结构。

    这个函数的基本结构完成以后,你可以通过另一个函数调用执行,也可以直接从 Python 命令提示符执行。

    如下实例调用了 printme() 函数:

    #!/usr/bin/python3 
    # 定义函数
    def printme( str ):
       # 打印任何传入的字符串
       print (str)
       return
     
    # 调用函数
    printme("我要调用用户自定义函数!")
    printme("再次调用同一函数")
    
    结果:
    我要调用用户自定义函数!
    再次调用同一函数
    

    1.3 参数

    以下是调用函数时可使用的正式参数类型:

    • 必需参数
    • 关键字参数
    • 默认参数
    • 不定长参数

    1.3.1 必需参数

    必需参数须以正确的顺序传入函数。调用时的数量必须和声明时的一样。

    不传参报错情况:

    #!/usr/bin/python3 
    #可写函数说明
    def printme( str ):
       "打印任何传入的字符串"
       print (str)
       return
     
    # 调用 printme 函数,不加参数会报错
    printme()
    
    结果:
    Traceback (most recent call last):
      File "test.py", line 10, in <module>
        printme()
    TypeError: printme() missing 1 required positional argument: 'str'
    

    1.3.2 关键字参数

    关键字参数和函数调用关系紧密,函数调用使用关键字参数来确定传入的参数值。

    使用关键字参数允许函数调用时参数的顺序与声明时不一致,因为 Python 解释器能够用参数名匹配参数值。

    以下实例在函数 printme() 调用时使用参数名:

    #!/usr/bin/python3 
    #可写函数说明
    def printme( str ):
       "打印任何传入的字符串"
       print (str)
       return
     
    #调用printme函数
    printme( str = "测试")
    
    结果:
    测试
    

    以下实例中演示了函数参数的使用不需要使用指定顺序:

    #!/usr/bin/python3
     #可写函数说明
    def printinfo( name, age ):
       "打印任何传入的字符串"
       print ("名字: ", name)
       print ("年龄: ", age)
       return
     
    #调用printinfo函数
    printinfo( age=50, name="test" )
    
    结果:
    名字:  test
    年龄:  50
    

    1.3.3 默认参数

    调用函数时,如果没有传递参数,则会使用默认参数。以下实例中如果没有传入 age 参数,则使用默认值:

    #!/usr/bin/python3
     
    #可写函数说明
    def printinfo( name, age = 35 ):
       "打印任何传入的字符串"
       print ("名字: ", name)
       print ("年龄: ", age)
       return
     
    #调用printinfo函数
    printinfo( age=50, name="test" )
    print ("------------------------")
    printinfo( name="test" )
    
    结果:
    名字:  test
    年龄:  50
    ------------------------
    名字:  test
    年龄:  35
    

    1.3.4 不定长参数

    1.3.4.1 不定长 *:元组

    如果需要一个函数能处理比当初声明时更多的参数。这些参数叫做不定长参数,和上述 2 种参数不同,声明时不会命名。基本语法如下:

    def functionname([formal_args,] *var_args_tuple ):
       "函数_文档字符串"
       function_suite
       return [expression]
    

    加了星号 * 的参数会以元组(tuple)的形式导入,存放所有未命名的变量参数

    #!/usr/bin/python3  
    # 可写函数说明
    def printinfo( arg1, *vartuple ):
       "打印任何传入的参数"
       print ("输出: ")
       print (arg1)
       print (vartuple)
     
    # 调用printinfo 函数
    printinfo( 70, 60, 50 )
    
    结果: 
    70
    (60, 50)
    

    如果在函数调用时没有指定参数,它就是一个空元组。我们也可以不向函数传递未命名的变量。如下实例:

    #!/usr/bin/python3 
    # 可写函数说明
    def printinfo( arg1, *vartuple ):
       "打印任何传入的参数"
       print ("输出: ")
       print (arg1)
       for var in vartuple:
          print (var)
       return
     
    # 调用printinfo 函数
    printinfo( 10 )
    printinfo( 70, 60, 50 )
    以上实例输出结果:
    
    输出:
    10
    输出:
    70
    60
    50
    

    声明函数时,参数中星号 * 可以单独出现,例如:

    def f(a,b,*,c):
        return a+b+c
    

    如果单独出现星号 *,则星号 * 后的参数必须用关键字传入
    参数列表里的 * 星号,标志着位置参数的就此终结,之后的那些参数,都只能以关键字形式来指定

    >>> def f(a,b,*,c):
    ...     return a+b+c
    ... 
    >>> f(1,2,3)   # 报错
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    TypeError: f() takes 2 positional arguments but 3 were given
    >>> f(1,2,c=3) # 正常
    6
    >>>
    

    1.3.4.2 不定长 **:字典

    还有一种就是参数带两个星号 **基本语法如下:

    def functionname([formal_args,] **var_args_dict ):
       "函数_文档字符串"
       function_suite
       return [expression]
    

    加了两个星号 ** 的参数会以字典的形式导入

    #!/usr/bin/python3  
    # 可写函数说明
    def printinfo( arg1, **vardict ):
       "打印任何传入的参数"
       print ("输出: ")
       print (arg1)
       print (vardict)
     
    # 调用printinfo 函数
    printinfo(1, a=2,b=3)
    
    结果:
    输出: 
    1
    {'a': 2, 'b': 3}
    

    1.3.5 匿名函数

    Python 使用 lambda 来创建匿名函数。
    所谓匿名,意即不再使用 def 语句这样标准的形式定义一个函数。

    • lambda 只是一个表达式,函数体比 def 简单很多。
    • lambda 的主体是一个表达式,而不是一个代码块。仅仅能在 lambda 表达式中封装有限的逻辑进去。
    • lambda函数拥有自己的命名空间,且不能访问自己参数列表之外或全局命名空间里的参数。
    • 虽然 lambda 函数看起来只能写一行,却不等同于 C 或 C++ 的内联函数,后者的目的是调用小函数时不占用栈内存从而增加运行效率。

    lambda函数的语法只包含一个语句,如下:

    lambda [arg1 [,arg2,.....argn]]:expression
    

    设置参数 a 加上 10:

    x = lambda a : a + 10
    print(x(5))
    
    结果:
    15
    

    以下实例匿名函数设置两个参数:

    #!/usr/bin/python3
    # 可写函数说明
    sum = lambda arg1, arg2: arg1 + arg2
     
    # 调用sum函数
    print ("相加后的值为 : ", sum( 10, 20 ))
    print ("相加后的值为 : ", sum( 20, 20 ))
    
    结果:
    相加后的值为 :  30
    相加后的值为 :  40
    

    我们可以将匿名函数封装在一个函数内,这样可以使用同样的代码来创建多个匿名函数。
    以下实例将匿名函数封装在 myfunc 函数中,通过传入不同的参数来创建不同的匿名函数:

    def myfunc(n):
      return lambda a : a * n
     
    mydoubler = myfunc(2)
    mytripler = myfunc(3)
     
    print(mydoubler(11))
    print(mytripler(11))
    
    结果:
    22
    33
    

    1.3.6 强制位置参数

    Python3.8 新增了一个函数形参语法 /用来指明函数形参必须使用指定位置参数,不能使用关键字参数的形式。

    def f(a, b, /, c, d, *, e, f):
        print(a, b, c, d, e, f)
    

    在例子中,形参 a 和 b 必须使用指定位置参数,c 或 d 可以是位置形参或关键字形参,而 e 和 f 要求为关键字形参:

    1.4 Python的 a, b = b, a+b

    Python中有一种写法:多个值同时赋给多个变量,如:a, b = b, a+b

    a = 0, b = 1
    a, b = b, a+b
    print a, b   #结果为:1 1
    

    这种写法,Python先计算 等号右边,再赋值,这样就保证了a, b都是初始值

    a = 0, b = 1
    a = b
    b = a + b
    print a, b   #结果为:1 2
    

    很明显,这种是顺序计算赋值,a+b中的a的值已经改变了

    2 面向对象

    Python从设计之初就已经是一门面向对象的语言,正因为如此,在Python中创建一个类和对象是很容易的

    2.1 类方法

    2.1.1 普通方法

    普通方法,默认有个self参数

    2.1.1.1 实例调用

    在类的内部,使用 def 关键字来定义一个方法,与一般函数定义不同,类方法必须包含参数 self, 且为第一个参数,self代表的是类的实例,而非类

    class Test:
        def prt(self):
            print(self)
            print(self.__class__)
     
    t = Test()
    t.prt()
    
    输出:
    <__main__.Test instance at 0x100771878>
    __main__.Test
    

    从执行结果可以很明显的看出,self 代表的是类的实例,代表当前对象的地址,而 self.class 则指向类

    2.1.1.2 类调用

    我们知道,实例方法的调用方式其实有 2 种,既可以采用类对象调用,也可以直接通过类名调用。
    通常情况下,我们习惯使用类对象调用类中的实例方法。但如果想用类调用实例方法,不能像如下这样:

    class CLanguage:
        def info(self):
            print("我正在学 Python")
    #通过类名直接调用实例方法
    CLanguage.info()
    运行上面代码,程序会报出如下错误:
    Traceback (most recent call last):
      File "D:\python3.6\demo.py", line 5, in <module>
        CLanguage.info()
    TypeError: info() missing 1 required positional argument: 'self'
    

    其中,最后一行报错信息提示我们,调用info() 类方式时缺少给 self 参数传参。这意味着,和使用类对象调用实例方法不同,通过类名直接调用实例方法时,Python 并不会自动给 self 参数传值。
    self 参数需要的是方法的实际调用者(是类对象),而这里只提供了类名,当然无法自动传值。
    因此,如果想通过类名直接调用实例方法,就必须手动为 self 参数传值。例如修改上面的代码为:

    class CLanguage:
        def info(self):
            print("我正在学 Python")
    clang = CLanguage()
    #通过类名直接调用实例方法
    CLanguage.info(clang)
    再次运行程序,结果为:
    我正在学 Python
    

    可以看到,通过手动将 clang 这个类对象传给了 self 参数,使得程序得以正确执行。实际上,这里调用实例方法的形式完全是等价于 clang.info()

    上面的报错信息只是让我们手动为 self 参数传值,但并没有规定必须传一个该类的对象,其实完全可以任意传入一个参数,例如:

    class CLanguage:
        def info(self):
            print(self,"正在学 Python")
    #通过类名直接调用实例方法
    CLanguage.info("zhangsan")
    运行结果为:
    zhangsan 正在学 Python
    

    可以看到,"zhangsan" 这个字符串传给了info() 方法的 self 参数。显然,无论是 info() 方法中使用 self 参数调用其它类方法,还是使用 self 参数定义新的实例变量,胡乱的给 self 参数传参都将会导致程序运行崩溃。

    总的来说,Python 中允许使用类名直接调用实例方法,但必须手动为该方法的第一个 self 参数传递参数,这种调用方法的方式被称为非绑定方法
    用类的实例对象访问类成员的方式称为绑定方法,而用类名调用类成员的方式称为非绑定方法。

    2.1.2 类方法

    类方法,默认有个 cls 参数(注意,绑定的不是类对象),可以被对象调用,需要加上 @classmethod 装饰器
    我们在调用类方法时,无需显式为 cls 参数传参。

    和 self 一样,cls 参数的命名也不是规定的(可以随意命名),只是 Python 程序员约定俗称的习惯而已

    class CLanguage:
        #类构造方法,也属于实例方法
        def __init__(self):
            self.name = "test"
            self.add = "test"
        #下面定义了一个类方法
        @classmethod
        def info(cls):
            print("正在调用类方法",cls)
    

    注意:如果没有@classmethod,则 Python 解释器会将 fly() 方法认定为实例方法,而不是类方法

    2.1.3 类静态方法

    静态方法,其实就是我们学过的函数,和函数唯一的区别是,静态方法定义在类这个空间(类命名空间)中,而函数则定义在程序所在的空间(全局命名空间)中。

    静态方法没有类似 self、cls这样的特殊参数,因此 Python 解释器不会对它包含的参数做任何类或对象的绑定。也正因为如此,类的静态方法中无法调用任何类属性和类方法。

    静态方法需要使用@staticmethod修饰,例如:

    class CLanguage:
        @staticmethod
        def info(name,add):
            print(name,add)
    

    静态方法的调用,既可以使用类名,也可以使用类对象,例如:

    #使用类名直接调用静态方法
    CLanguage.info("test","测试")
    #使用类对象调用静态方法
    clang = CLanguage()
    clang.info("Python","测试Python")
    运行结果为:
    test 测试
    Python 测试Python
    

    在实际编程中,几乎不会用到类方法和静态方法,因为我们完全可以使用函数代替它们实现想要的功能,但在一些特殊的场景中(例如工厂模式中),使用类方法和静态方法也是很不错的选择

    2.2 继承

    2.2.1 单继承

    Python 同样支持类的继承,如果一种语言不支持继承,类就没有什么意义。派生类的定义如下所示:

    class DerivedClassName(BaseClassName):
        <statement-1>
        .
        <statement-N>
    

    子类(派生类 DerivedClassName)会继承父类(基类 BaseClassName)的属性和方法。

    BaseClassName(实例中的基类名)必须与派生类定义在一个作用域内。除了类,还可以用表达式,基类定义在另一个模块中时这一点非常有用:
    class DerivedClassName(modname.BaseClassName):

    #!/usr/bin/python3
    #类定义
    class people:
        #定义基本属性
        name = ''
        age = 0
        #定义私有属性,私有属性在类外部无法直接进行访问
        __weight = 0
        #定义构造方法
        def __init__(self,n,a,w):
            self.name = n
            self.age = a
            self.__weight = w
        def speak(self):
            print("%s 说: 我 %d 岁。" %(self.name,self.age))
     
    #单继承示例
    class student(people):
        grade = ''
        def __init__(self,n,a,w,g):
            #调用父类的构函
            people.__init__(self,n,a,w)
            self.grade = g
        #覆写父类的方法
        def speak(self):
            print("%s 说: 我 %d 岁了,我在读 %d 年级"%(self.name,self.age,self.grade))
     
    s = student('ken',10,60,3)
    s.speak()
    
    输出结果为:
    ken 说: 我 10 岁了,我在读 3 年级
    

    2.2.2 多继承

    Python同样有限的支持多继承形式。多继承的类定义形如下例:

    class DerivedClassName(Base1, Base2, Base3):
        <statement-1>
        .
        <statement-N>
    

    需要注意圆括号中父类的顺序,若是父类中有相同的方法名,而在子类使用时未指定,python从左至右搜索 即方法在子类中未找到时,从左到右查找父类中是否包含方法。

    #!/usr/bin/python3
     
    #类定义
    class people:
        #定义基本属性
        name = ''
        age = 0
        #定义私有属性,私有属性在类外部无法直接进行访问
        __weight = 0 #前面是两个下划线
        #定义构造方法
        def __init__(self,n,a,w):
            self.name = n
            self.age = a
            self.__weight = w
        def speak(self):
            print("%s 说: 我 %d 岁。" %(self.name,self.age))
     
    #单继承示例
    class student(people):
        grade = ''
        def __init__(self,n,a,w,g):
            #调用父类的构函
            people.__init__(self,n,a,w)
            self.grade = g
        #覆写父类的方法
        def speak(self):
            print("%s 说: 我 %d 岁了,我在读 %d 年级"%(self.name,self.age,self.grade))
     
    #另一个类,多重继承之前的准备
    class speaker():
        topic = ''
        name = ''
        def __init__(self,n,t):
            self.name = n
            self.topic = t
        def speak(self):
            print("我叫 %s,我是一个演说家,我演讲的主题是 %s"%(self.name,self.topic))
     
    #多重继承
    class sample(speaker,student):
        a =''
        def __init__(self,n,a,w,g,t):
            student.__init__(self,n,a,w,g)
            speaker.__init__(self,n,t)
     
    test = sample("Tim",25,80,4,"Python")
    test.speak()   #方法名同,默认调用的是在括号中参数位置排前父类的方法
    
    输出结果为:
    我叫 Tim,我是一个演说家,我演讲的主题是 Python
    

    2.3 方法重写

    如果父类方法的功能不能满足需求,可以在子类重写父类的方法,实例如下:

    #!/usr/bin/python3
     
    class Parent:        # 定义父类
       def myMethod(self):
          print ('调用父类方法')
     
    class Child(Parent): # 定义子类
       def myMethod(self):
          print ('调用子类方法')
     
    c = Child()          # 子类实例
    c.myMethod()         # 子类调用重写方法
    super(Child,c).myMethod() #用子类对象调用父类已被覆盖的方法
    
    输出结果为:
    调用子类方法
    调用父类方法
    

    super(当前类名, self).函数()super()函数是用于调用父类(超类)的一个方法

    2.4 类私有属性方法和专有方法

    2.4.1 类的私有属性

    __private_attrs:两个下划线开头,声明该属性为私有,不能在类的外部被使用或直接访问。在类内部的方法中使用时 self.__private_attrs

    2.4.2 类的私有方法

    __private_method两个下划线开头,声明该方法为私有方法,只能在类的内部调用 ,不能在类的外部调用。self.__private_methods

    #!/usr/bin/python3
     
    class JustCounter:
        __secretCount = 0  # 私有变量
        publicCount = 0    # 公开变量
     
        def count(self):
            self.__secretCount += 1
            self.publicCount += 1
            print (self.__secretCount)
     
    counter = JustCounter()
    counter.count()
    counter.count()
    print (counter.publicCount)
    print (counter.__secretCount)  # 报错,实例不能访问私有变量
    
    输出结果为:
    1
    2
    2
    Traceback (most recent call last):
      File "test.py", line 16, in <module>
        print (counter.__secretCount)  # 报错,实例不能访问私有变量
    AttributeError: 'JustCounter' object has no attribute '__secretCount'
    

    2.4.3 类专有方法

    类的专有方法(前后两个下划线):

    • _new__:在实例创建之前被调用的,因为它的任务就是创建实例然后返回该实例对象,是个静态方法
      依照Python官方文档的说法,__new__方法主要是当继承一些不可变class时(比如int, str, tuple), 提供给你一个自定义这些类的实例化过程的途径。还有就是实现自定义的metaclass
    • __init__ : 当实例对象创建完成后被调用的,然后设置对象属性的一些初始值,通常用在初始化一个类实例的时候。是一个实例方法
    • __del__: 析构函数,释放对象时使用
    • __repr__: 打印,转换
    • __setitem__: 按照索引赋值
    • __getitem__: 按照索引获取值
    • __len__: 获得长度
    • __cmp__: 比较运算
    • __call__: 函数调用
    • __add__: 加运算
    • __sub__: 减运算
    • __mul__: 乘运算
    • __truediv__: 除运算
    • __mod__: 求余运算
    • __pow__: 乘方

    相关文章

      网友评论

          本文标题:Python基础之函数,面向对象

          本文链接:https://www.haomeiwen.com/subject/vgvjtdtx.html