目录

前言

函数--实现代码复用

(1)定义函数

(2)函数调用

(3)函数参数

(4)传参解包

(5)作用域

类--实现代码复用

(1)类的创建使用

(2)私有、公有成员

(3)数据成员和成员方法

(4)属性

(5)类与对象动态混入机制

(6)继承

(7)多态


前言

是否遇到过有很多操作是完全相同或者非常相似的情况,他们之间区别仅仅在于数据不同,不同的代码位置多次执行相似甚至完全相同的代码块。在这里,解决此类问题,可以选择借助“函数”技巧和“类”的手段。

函数--实现代码复用

(1)定义函数

实现代码复用的第一种手段就是使用函数。在Python中,使用def关键词来定义函数,给予函数名和括号,括号内可填补此函数形参列表,尾接冒号,隔行加上注释,再编写函数体,即可完成了一个函数的整体定义。

函数的定义格式:def 函数名 ( [参数列表] ):

                                    '''注释'''

                                    函数体

语法上注意:与C语言有个很大不同,Python函数形参不需要声明其类型,解释器会自行判断,也不需要指定函数的返回值类型,return语句结束函数执行的同时可返回任意类型值,这取决于返回表达式的类型。若未执行return语句结束函数,Python将认为该函数以return None结束,返回为空值。

适当注释可方便用户得到提示,使用内置函数help()可查看函数的使用帮助。

(2)函数调用

函数调用时,应以下注意几点要求:

1.匹对调用函数的参数,如果传入的参数数量或者参数的类型不对,会报TypeError的错误;

2.函数定义后才可调用,须注意编译器编译顺序问题;

3.形参位置对应,传递的实参与形参在位置上是一一匹配的。

def afunctions (an,ai):
    if an==ai or an==0:
        return an+ai
    elif an not in [3,2,5]:
        return an-ai
>>> afunctions(75,75)
150

递归调用是函数调用的特殊情况,可实现层层调用自己进行运算。

def fact(n):    #递归函数
    return fact_iter(n, 1)

def fact_iter(num, product):
    if num == 1:
        return product
    return fact_iter(num - 1, num * product)

(3)函数参数

Python支持默认值参数,用户在定义函数时可设置默认值,默认值参数传递实参是可选的。不过需要注意:def  函数名  (......,形参名 = 默认值):任何一个默认值参数右边都不能出现没有默认值的普通位置参数。使用“函数名.__defaults__”可随时依次查看函数所有默认参数的当前值,返回值为元组形式。

def afunctions (an=6,ai=2):
    print(an);print(ai)
>>> afunctions.__defaults__
(6, 2)

函数的默认值参数是在函数定义时确定的,多次调用函数并且不为默认值参数传值,默认参数只在函数定义时进行一次解释和初始化。

>>> an = 5
>>> def func(n = an):
...     print(n)
>>> func()
5
>>> an=7
>>> func()
5

由于Python基于值的内存管理模式,在调用函数时,实参到形参的过程同样完成的是引用的传递,即Python函数不存在传值调用。如果传递给函数的是列表、字典、集合或其他可变序列,并且在函数内部使用下标等方式可对序列进行更改时,其结果是可以反应到函数之外的。

函数传值也可采用关键参数的参数传递方式,明确指定哪个值传递给哪个参数,这种情况,实参顺序是可以与形参顺序不一样的。

def afunctions (an,ai):
    print(an);print(ai)
>>> afunctions(ai=23,an=45)
45
23

定义函数时,也可采用*parameter和**parameter可变长度参数形式,*parameter将接受任意多个实参合并放在一个元组中,**parameter将接受类似于关键参数一样显示赋值形式的多个参数合并放入字典。这种方式函数设计不恰当很可能出现报错。 

(4)传参解包

序列解包对象是指实参,与可变长度相似,存在*与**形式。

对于序列,元组、集合等可迭代对象作为实参,使用*可对序列进行解包,并按顺序依次分别传递给形参。实参对象是字典,默认是对字典的“键”操作。

def afunctions (an=6,en=2,un=7):
    print(an);print(en);print(un)
>>> sen = {2:'apple',3:'banana',4:'cherry'}
>>> afunctions(*sen.values())
apple
banana
cherry

采用**形式进行关键参数解包,与关键参数功能相似,若应用于字典,要求字典中所有的键都必须与函数形参相对应。另外注意,序列解包与关键参数解包可同时使用,序列解包不能放在关键参数解包之后。

(5)作用域

变量起作用的代码范围成为变量的作用域,不同的作用域内同名变量之间互不干扰。

这里提及到作用域的问题,不仅要考虑到函数的作用域,还需考虑到变量的作用域。这一个Python的程序文件中,函数的作用域为整个文件,当然还要根据实际情况,要在函数定义后调用。

而变量可分为两种,全局变量和局部变量,在某个函数中普通定义的变量为局部变量,作用域也就是这个函数内,函数调用结束,该变量引用内存被Python释放,全局变量则不同,它的作用域在这整个程序文件。在函数内部,可通过global关键词声明或者定义全局变量。

局部变量若与全局变量同名时,那么局部变量会在自己的作用域内暂时隐藏同名的全局变量。

>>> def demonate():
...     an = 5; en = 7
...     print(an,en)
...
>>> an = 8
>>> demonate()
5 7
>>> en
'''Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'en' is not defined. Did you mean: 'an'?'''

类--实现代码复用

Python是面向对象的解释型高级动态编程语言,完全支持面向对象的基本功能,面向对象程序设计的关键就是如何合理地定义类并且组织多类之间的关系。在Python中,一切内容都可以称之为对象,函数是对象,类亦是对象。

(1)类的创建使用

Python使用class关键词来定义类,类的名称首字母要用大写形式,后面括号为继承,派生其他基类的格式要求,若无定义时可去掉,基本格式参考:

class Student:      Student(object):

'''   注释     '''

    total = 0 
    def __init__(self, name, score):
        self.name = name
        self.score = score

    def print_score(self):
        print('%s: %s' % (self.name, self.score))

     ......

定义类后,可以用来实例化对象,通过“对象名.成员”来访问类中的数据成员或成员方法。在Python中使用内置函数isinstance()来测试对象是否为某个类的实例,或者可以采用type()来查看其类型。同时提供pass关键词在类和函数或者选择结构中表示空语句,实现“占位”功能,为将来编写补充内容预留空间,pass执行时Python会将其跳过,什么也不会发生。

补充:“.”为成员访问运算符,用来访问命名空间、模块,对象成员等,在Python中可以使用内置函数dir()查看指定对象、模块、命名空间等的所有成员。

(2)私有、公有成员

类的成员分为私有成员和公有成员两种。私有成员在类的外部不能被直接访问,一般只能在类的内部访问与其他操作。而公有成员是公开使用,类的内部和外部均可访问操作。当然,在类的外部通过调用对象的公有成员方法来访问私有成员是被允许的,或者通过特殊方式“对象名._类名__xxx”在程序外部访问私有成员,不过这会破坏类的封装性。

'''这段为上面的接续,若不注意成员属性,在访问时Python会报错'''
>>> bart = Student('Bart Simpson', 59)
>>> bart.__name
'''Traceback (most recent call last):
  File "<pyshell>", line 1, in <module>
AttributeError: 'Student' object has no attribute '__name'   '''
>>> bart.print_score()
Bart Simpson: 59

 在类中,类的成员命名定义有明显区分。

1)系统定义的特殊成员,__xxx__前后各两个下划线;

(2)__xxx,以两个下划线或多个开头,一般表示私有成员;

(3)_xxx,以一个下划线开头,为受保护成员,只有类对象和子类对象才可访问。

(3)数据成员和成员方法

类中的数据成员分为属于对象的数据成员和属于类的数据成员,属于类的数据成员为该类所有对象可共享的,不属于任何一个对象。而属于对象的数据成员一般在构造方法“__init__()”中定义,当然也可以在其他成员方法中定义,在定义和在实例方法中访问数据成员时以“self”作为前缀,同一个类的不同对象(实例)的数据成员之间互不影响。

举个实例,在类的创建时,total就是一个属于类的数据成员,而name、score就是属于对象的数据成员。

在面向对象程序设计中,函数和方法是两个概念,他们有本质区别。方法一般指与特定实例绑定的函数,通过对象调用的方法时,对象本身将作为第一个参数自动传递过去,而普通函数并不具备此特点。 

Python类的成员方法大致可以分为实例方法(公有方法、私有方法)、静态方法(@staticmethod)、类方法(@classmethod)、和抽象方法,这里不做具体详解。

补充:

__init__方法:Python类中的特殊方法,方法名的开始和结束都有双下划线,该方法为构造方法,当创建类的对象时,它被自动调用。__init__方法可以声明类所产生的对象属性,并为其赋初始值。它是用来构造对象的,被调用后实例化了该类型的对象。

说明一下:Python的特殊方法比较常见的是构造方法和析构方法,__init__()是构造方法,用来为数据成员设置初始值或进行必要的初始化工作,在实例化对象时自动调用执行。

另外,析构方法__del__()用来删除对象回收对象内存空间,用户在未编写析构方法时,Python会提供一个默认的析构方法。

self参数:类的实例方法按惯例有一个名为self的参数,并且为第一个形参,self参数代表将来要创建的对象本身。在类的方法中访问实例变量(数据成员)时需要以self为前缀。外部通过对象调用对象方法时并不需要传递这个参数,如果在外部通过类调用对象方法则需要显示为self参数传值。

(4)属性

Python类中公开的数据成员在外部可以随意访问和修改,很难保证用户进行修改时提供新数据的合法性,就是说混入的数据很容易被破坏。属性(property)是一种特殊的成员方法,结合公开数据成员和成员方法优点,既能实现数据成员的灵活访问,又能对值进行必要检查。正确使用类的属性,能提高其封装性和完整性。

class Student(object):
    total=100
    def __init__(self, name, score):
        self.__name = name
        self.__score = score

    @property   #修饰器,定义属性,提供对私有数据成员的访问
    def print_score(self):    #只读属性,无法做修改和删除
        print('%s: %s' % (self.__name, self.__score))

student1 = Student('Liang',100)
'''采用上方对私有成员修饰成只读属性,或者之间在类定义时,设置对象属性'''
print_score.property(__get,__set)    #设置可读、可写属性,指定相应的读写方法
print_score.property(__get,__set,__del)    #设置可读、可写、可删除属性

(5)类与对象动态混入机制

在Python中。我们可以动态地自定义类和对象增加数据成员和成员方法。

class Student(object):
    total=100
    def __init__(self, name, score):
        self.__name = name
        self.__score = score

    def print_score(self):
        print('%s: %s' % (self.__name, self.__score))

student1 = Student('Liang',100)

>>> Student.numbers = 200    #增加类的数据成员
>>> student1.score = 99    #修改类中属于对象的数据成员
>>> def grade(self,ssa):
    self.gradenumber = ssa
    
>>> student1.grade = types.MethodType(grade,student1)    #动态增加类的对象成员方法
>>> Student.grade = grade    #动态基类添加类的对象成员方法
>>> student1.grade(152020)    #调用类的对象成员
>>> print(student1.gradenumber)
152020

Python类型的动态性使得我们可以动态为自定义类及其对象增加新的属性和行为,俗称混入机制。 

(6)继承

在面向对象程序设计中,定义一个class时,可以从某个现有的class继承,新的class称为子类或派生类,而被继承的class称为基类、父类或超类。派生类可以继承父类所有的公有成员,这也是继承的最大好处。

class Student(object):
    def __init__(self, name, score):
        self.__name = name
        self.__score = score
    def print_score(self):
        print('%s: %s' % (self.__name, self.__score))

class Student1(Student):
    pass

>>> student1 = Student1('wang',150)
>>> student1.print_score()
wang: 150

(7)多态

可以说继承的第二个好处就是“多态”,“多态”指的是基类的同一方法在不同派生类对象中具有不同的表现和行为。举个例子,算术运算符“+”在做算术运算时表示加法,对象是字符串表示字符串的连接。

Python大多数运算符可以作用于多种不同类型的操作数,并且对于不同类型往往有不同的表现形式,这本身就是多态。当然,这是通过特殊方法和运算符重载实现的。

派生类与基类存在同名方法时,派生类会覆盖基类。

class Student(object):
    def print_score(self):
        print('A')

class Student1(Student):
    def print_score(self):
        print('B')
class Student2(Student):
    def print_score(self):
        print('C')
class Student3(Student):
    pass

x = [item() for item in (Student,Student1,Student2,Student3)]
for item in x:
    item.print_score()

>>> %Run running.py
A
B
C
A