zoukankan      html  css  js  c++  java
  • python(二):函数

    一、函数、名称空间与作用域

      1.函数的构成

        python有三种层次的抽象:(1)程序可分成多个模块;(2)每个模块包含多条语句;(3)每条语句对对象进行操作。函数大致处于第二层。函数有它的定义格式、参数、逻辑代码块、返回值、以及函数属性五部分组成。

    1 def foo(name):  # foo: 别名; (),执行规则; name, 形式参数
    2     """doc"""   # 说明文档,可以用foo.__doc__获取
    3     return name # 返回值
    4 print(foo("foo"))  # 填写形式参数对应的实体参数,并执行foo函数
    5 f = foo      # 函数别名可以赋值
    6 print(f("foo"))
    View Code

      2.函数与方法

        函数和方法是有一些区别的,提到方法一般指的是某个对象的方法。因为python自上而下执行,所以函数不可以提前声明。但是方法可以,例如类对象中,可以直接调用后面的方法。但当方法或者函数被调用时,都是函数。

    1 class Foo:
    2     def pri(self):
    3         return foo()
    4     def foo(self, name):
    5         return name
    6 f = Foo()
    7 f.pri("foo")
    View Code

      3.名称空间与作用域

        - 名称空间

          名称空间是站在程序的角度对抽象的结构进行划分。

          python使用名称空间(namespaces)的概念在应用程序上储存关于对象的信息和位置。内置函数、语句和对象有特殊的命名空间,函数、模块有单独的命名空间,也就是两者有各自的命名空间,及特定的抽象级别。在特定的抽象级别上访问对象或函数时,都将搜寻名称空间表以获得可引用的特定名字,并确定处理存储在该位置的信息的方法。

          函数的参数,加上函数体中绑定的所有变量(通过赋值或者其它绑定语句,如def),功能构成了该函数的本地命名空间(local namespace),也称为本地范围(local space)。所有这些也称为该函数的本地变量(local variable)。

          其它则是全局名称空间,包括可被其它函数或模块直接调用的、存活于整个程序运行期间的变量、函数、模块等。

        - 作用域

          作用域是站在对象的角度查看它的适用范围。在python中,作用域遵循以下规则:

            - 每个模块都有自己的作用域,这意味着多重模块有它们自己的作用域,从而形成自己的名名称空间。

            - 定义新函数时创建新的作用域,作用域仅限于函数体内。

            - 每个未缩进的逻辑代码块,它的作用域是全局的。

      4.全局变量和局部变量

        定义在最高级别的模块中的变量,被称为全局变量;定义在局部作用域中的变量被称为局部变量。一个变量的作用域和它寄宿的名称空间相关。

        python变量遵循LGB规则:

          - 变量引用依次搜寻3种作用域:局部(Local)、全局(Global)、内置(Built-in)。

          - 在局部作用域内对变量赋值时会创建新对象或更新对象,如果想在局部作用域对全局对象赋值,必须使用关键字global。

          - 全局声明会把赋值名字映射到封装的模块的作用域。这意味着,要么显示地从输入的模块中输入对象,要么使用完全有效的模块/对象名。

        示例一:GLB规则

    1 a = 20
    2 def func():
    3     print(a)  # 在局部作用域找不到a,会继续从全局作用域找a
    4 func()
    5 # 打印ax  
    View Code

        示例二:全局变量与局部变量的互不干扰

     1 a = 20  # 全局变量
     2 def func():
     3     b = 25
     4     a = 30
     5     global c
     6     c = 125
     7     print(a)
     8     print(b)
     9 print(a)
    10 func()
    11 print(c)
    12 print(a)
    13 # 打印结果为:
    14 20
    15 30
    16 25
    17 125
    18 20
    View Code

        也就是说,创建局部变量a,是直接开辟内存生成对象30,然后引用给a。它与全局变量的a毫不相关。global可以修改局部变量为全局变量。

        globals()函数和locals()函数可以直接查看当前文件中的全局变量和局部变量。

      5、参数

        python函数中的传递参数,是传递变量的值本身,而不是创建一个新的引用。

        在定义函数时,有位置参数,默认参数,以及扩展参数;在调用函数时,可以按位置传递相应的变量,也可以按键值对传递相应的参数,也可以以扩展的方式传递参数。

        在定义函数时,位置参数、默认参数与扩展参数的顺序是:位置参数 > 扩展元组 > 默认参数 > 扩展字典。

    1 def func(a, b, *args, author="jingyu", **kwargs):
    2     print(a)
    3     print(b)
    4     print(args)
    5     print(author)
    6     print(kwargs)
    View Code
    1 func(123, 456, 7,8,9, gender="female", age=20)
    2 # 打印结果为:
    3 123
    4 456
    5 (7, 8, 9)
    6 jingyu
    7 {'gender': 'female', 'age': 20}
    View Code

        在定义函数时,*args会把所有位置参数匹配剩余的变量组合成元组,**kwargs会把关键字参数(键值对)组合成一个字典。

        在执行函数时,*list或者*tuple会把所有的元素展开成单独的位置参数,**dict会把字典内的键值对展开成关键字参数。

        这个功能在写闭包和装饰器的时候非常有用。

     1 func(
     2     123, 456, 
     3     *[7, 8, 9], *[10, 11, 12], *{"name1": "sunbin", "name2": "mengtian"}, 
     4     **{"name3": "baiqi", "gender": "female", "age": 72},
     5 )
     6 # 打印结果为:
     7 123
     8 456
     9 (7, 8, 9, 10, 11, 12, 'name1', 'name2')
    10 jingyu
    11 {'name3': 'baiqi', 'gender': 'female', 'age': 72}
    View Code

      6、函数属性

        可以给函数创建一些属性,并通过func.__dict__来查看。函数属性应用在高度专业的应用程序中。

     1 def say():
     2      print("hello, my man.")
     3  # 接上面定义的func
     4  func.__live__ = 20
     5  func.__create__ = "2018_04_18"
     6  func.say = say
     7  print(func.__dict__)
     8  # 打印结果为:
     9  {'__create__': '2018_04_18', '__live__': 20, 'say': <function __main__.say>}
    10 # 显然它可以执行
    11 func.say()
    12 # 结果显示
    13 hello, my man.
    14 # 这个和js的匿名函数有些类似,但是不支持func.say = function (){console.log("hello, my man.)} 这种写法
    View Code

    二、闭包

      闭包是内层函数对层函数局部变量的引用。简单来说,闭包就是内部包含函数的函数。

      闭包的好处:如果python检测到闭包,它有一个机制,局部作用域不会随着函数的结束而结束。可以在局部作用域添加缓存机制,使得对于计算量较大时能够提高效率。闭包也是装饰器的前提。

      1、创建闭包

    1 def outer(name):
    2     def inner(age):
    3         return "hello, i'm %s, %s." % (name, age)
    4     return inner
    5 outer("baiqi")(72)
    6 # 打印结果为:
    7 "hello, i'm baiqi, 72."
    View Code

      可以看到,outer("baiqi")的返回值是一个函数对象,即inner,inner可以接收参数并执行,它的返回值是真正要打印的结果。可以隐隐地感觉到,outer这个外层函数实际是在打印真正结果之前做了一些额外的"修饰"工作。

      2、闭包中的外层变量和内层变量也遵循GLB规则

     1  def outer():
     2      a = 100
     3      def inner1():
     4          return a * 5
     5      b = inner1()
     6      def inner2():
     7          return b * 10
     8      a = inner2()
     9      def inner3():
    10         b = a
    11         print(a, b)
    12     inner3()
    13     print(a, b)
    14 outer()
    15 # 打印结果为:
    16 5000 5000
    17 5000 500
    View Code

        闭包里可以写很多逻辑代码和函数,闭包并不一定要返回某个具体的内层函数,闭包有着很好的灵活性和弹性,它相当于一个工作室。

    三、装饰器

      1、装饰器函数的推导

        重写一下白起打招呼:

    1 import time
    2 def hello():
    3     print("Hello, i'm baiqi, 72 years old.")
    4 
    5 def timer():
    6     t = time.strftime("%Y-%m-%d %X", time.localtime())
    7     print(t)
    8     return hello()
    View Code

        把它改写成闭包的形式:

    1 def timer():
    2     t = time.strftime("%Y-%m-%d %X", time.localtime())
    3     print(t)
    4     def fn():
    5         return hello()
    6     return fn
    7 timer()()
    View Code

        前面说到outer("baiqi")返回的是一个待执行的函数,这里也是,timer()返回的是fn,fn()执行时返回hell()执行的结果。于是正常显示:

    1 2018-04-18 23:03:54
    2 Hello, i'm baiqi, 72 years old.

        hello这个返回值不够灵活,把hello函数作为对象传进来;并且timer()运行时直接打印了t,它是timer()函数的运行时间而不是hello的,于是改写为:

    1 def timer(f):
    2     def fn():
    3         t = time.strftime("%Y-%m-%d %X", time.localtime())
    4         print(t)
    5         return f()
    6     return fn
    7 timer(hello)()
    View Code

        此时如果要只写hello()函数就打招呼,而不是用timer(hello)()这个额外的函数名,可以改写为:

     1 import time
     2 def hello():
     3     print("Hello, i'm baiqi, 72 years old.")
     4 def timer(f):
     5     def fn():
     6         t = time.strftime("%Y-%m-%d %X", time.localtime())
     7         print(t)
     8         return f()
     9     return fn
    10 hello = timer(hello)
    11 print(hello, hello.__name__)
    12 hello()
    13 # 打印结果为:
    14 <function timer.<locals>.fn at 0x10f201d08> fn
    15 2018-04-18 23:12:57
    16 Hello, i'm baiqi, 72 years old.
    View Code

        在hello = timer(hello)这一步,把原来的hello函数作为参数传递给timer,那么timer(hello)的返回值就是未执行的fn函数。fn函数在执行时,也就是timer(hello)() = fn() = hello(),会返回print字符串。

        在python中,用@符号来代表hello = timer(hello)这个关系式,并要求def hello()写在它的下面。即:

     1 import time
     2 def timer(f):
     3     def fn():
     4         t = time.strftime("%Y-%m-%d %X", time.localtime())
     5         print(t)
     6         return f()
     7     return fn
     8 @timer
     9 def hello():
    10     print("Hello, i'm baiqi, 72 years old.")
    11 hello()
    View Code

        注意:逻辑代码写内层函数里面。上面将t写在外面时,有运行timer()会直接打印调用时间,这个需要避免。可以在fn函数的内部尽情地写if for while 等等代码块,也不一定非要返回f()。

        可以看出装饰器的作用:在尽量保留被装饰函数的基础上,不着痕迹地添加一些额外的功能。“尽量保留”说明它的确修改了原函数的一些内容,如函数名称(fn而不是hello),

    1 print(hello.__name__)
    2 # 打印结果为:
    3 fn
    View Code

        “不着痕迹”是指不要在外层函数的局部作用域内写逻辑代码,除非业务需要。

      2、装饰器函数传递参数

     1 import time
     2 def timer(f):
     3     def fn(name, age):
     4         t = time.strftime("%Y-%m-%d %X", time.localtime())
     5         print(t)
     6         return f(name, age)
     7     return fn
     8 @timer
     9 def hello(name, age):
    10     print("Hello, i'm %s, %d years old." % (name, age))
    11 hello("wangjian", 62)
    View Code

        因为fn返回和执行hello本身,所以在hello中传递参数,就需要在fn中传递参数。为了使fn和返回执行的f函数(就是hello)具有更大的灵活性,通常会这么写:

     1 import time
     2 def timer(f):
     3     def fn(*args, **kwargs):
     4         t = time.strftime("%Y-%m-%d %X", time.localtime())
     5         print(t)
     6         return f(*args, **kwargs)
     7     return fn
     8 @timer
     9 def hello(name, age):
    10     print("Hello, i'm %s, %d years old." % (name, age))
    11 hello("limu", 62)
    View Code

        前面已提到*args和**kwargs在定义函数和执行函数的功能。这里再重申一遍:在定义函数时,*args会将位置参数上传递的元素组合成元组,该元组赋给了变量args;在执行函数时,*args(此时args是一个元组(limu, 62))会把元组展开成元素。于是name还是那个name,age还是那个age。

     1 class Cla(object):
     2     def __init__(self, *args, **kwargs):
     3         # l1 = *args  # SyntaxError can't use starred expression here
     4         print(*args)
     5         # l1 =  args  # l1 = (1, 2, 3)
     6         # l1, l2, l3 = args   # l1 = 1
     7         print(*kwargs)
     8     def __call__(self):
     9         print("hello, world!")
    10     
    11 def func(*args, **kwargs):
    12     return Cla(*args, **kwargs)
    13 
    14 func(1,2,3, name=123, age=123)()
    View Code

        这也说明,fn的参数和f的参数必须保持一致。

      3、装饰器上带参数

     1 import time
     2 def hello(name, age):
     3     print("Hello, i'm %s, %d years old." % (name, age))
     4 def gender(male=False):
     5     def timer(f):
     6         def fn(*args, **kwargs):
     7             t = time.strftime("%Y-%m-%d %X", time.localtime())
     8             print(t)
     9             f(*args, **kwargs)
    10             if male:
    11                 print(" I'm hero.")
    12             else:
    13                 print("I'm beautiful girl.")
    14         return fn
    15     return timer
    16 f1 = gender(male=True)  # f1就是gender函数执行的返回值,也就是timer
    17 f2 = f1(hello)          # f2就是timer(f)函数执行的返回值,也就是fn
    18 f2("limu", 62)
    View Code

         改写一下丑陋的f1和f2:

    1 gender = gender(male=True)  # f1就是gender函数执行的返回值,也就是timer
    2 hello = gender(hello)          # f2就是timer(f)函数执行的返回值,也就是fn
    3 hello("limu", 62)

        再把gender和hello写到一行:

    1 hello = gender(male=True)(hello)

        它符合装饰器的写法,即如果有hello=timer(hello),那么有@timer。于是这里可写为@gender(male=True)。最后,杨玉环打招呼了。

     1 import time
     2 def gender(male=False):
     3     def timer(f):
     4         def fn(*args, **kwargs):
     5             t = time.strftime("%Y-%m-%d %X", time.localtime())
     6             print(t)
     7             f(*args, **kwargs)
     8             if male:
     9                 print(" I'm hero.")
    10             else:
    11                 print("I'm beautiful girl.")
    12         return fn
    13     return timer
    14 @gender(male=False)
    15 def hello(name, age):
    16     print("Hello, i'm %s, %d years old." % (name, age))
    17 hello("yangyuhuan", 62)
    View Code

        打印结果为:

    1 2018-04-19 00:07:25
    2 Hello, i'm yangyuhuan, 62 years old.
    3 I'm beautiful girl.
    View Code

      4、装饰器的叠加:

        此时,又有一个要求,要打印这个人物的国家。可以在gender里再添加一个参数,在fn函数里去写细节的逻辑代码。现在用另一种方式实现要求:

     1 import time
     2 def timer(f):
     3     def fn(*args, **kwargs):
     4         t = time.strftime("%Y-%m-%d %X", time.localtime())
     5         print(t)
     6         f(*args, **kwargs)
     7         return args
     8     return fn
     9 def country(f):
    10     def fn(*args, **kwargs):
    11         variables = f(*args, **kwargs)
    12         if args[0] == "yangyuhuan":
    13             print("A beautiful girl, from TangChao.")
    14         else:
    15             print("A hero from ZhanGuo.")
    16     return fn
    17 @country
    18 @timer
    19 def hello(name, age):
    20     print("Hello, i'm %s, %d years old." % (name, age))
    21 hello("yangyuhuan", 24)
    View Code

        打印结果为:

    1 2018-04-19 00:17:59
    2 Hello, i'm yangyuhuan, 24 years old.
    3 A beautiful girl, from TangChao
    View Code

        注意:两个装饰器叠加,第一个装饰器最好写返回值。叠加的装饰器由上而下装饰,由下而上执行。

      5、漏掉的functools

        前面提到hello.__name__是fn,即内层的那个函数名。这在框架里面是不允许出现的,于是有了个functools.wraps()方法能完整保留被装饰函数的完整信息。它的用法也十分简单。

     1 import time, functools
     2 def timer(f):
     3     @functools.wraps(f)
     4     def fn():
     5         t = time.strftime("%Y-%m-%d %X", time.localtime())
     6         print(t)
     7         return f()
     8     return fn
     9 @timer
    10 def hello():
    11     print("Hello, i'm baiqi, 72 years old.")
    12 print(hello.__name__)
    13 # 打印结果为
    14 hello
    View Code

      6、类装饰器

        类装饰器写法一:

     1 import functools, time
     2 class Add(object):
     3     def __call__(self, f):
     4         @functools.wraps(f)
     5         def decorator(*args, **kwargs):
     6             t1 = time.time()
     7             outcome = f(*args , **kwargs)
     8             time.sleep(1)
     9             t2 = time.time()
    10             print("t: %r " % (t2 - t1))  
    11         return decorator
    12 
    13 @Add()
    14 def add(var1, var2):
    15     return var1 * var2
    16 add(4, 5)
    View Code

        类装饰器写法二:

     1 class Add(object):
     2     def add(self,f):
     3         @functools.wraps(f)
     4         def fn(var1, var2):
     5             t1 = time.time()
     6             outcome = f(var1 , var2)
     7             t2 = time.time()
     8             print("t: %r " % (t2 - t1))
     9             return outcome
    10         return fn
    11 myAdd = Add()
    12 @myAdd.add
    13 def add(var1, var2):
    14     return var1 * var2
    15 add(4, 5)
    View Code

        类装饰器写法三:

     1 class Add(object):
     2     def __init__(self, appname):
     3         self.appname = appname
     4         self.function_dict = {}
     5     def add(self,route):
     6         def decorator(f):
     7             @functools.wraps(f)
     8             def fn(*args, **kwargs):
     9                 t1 = time.time()
    10                 outcome = f(*args, **kwargs)
    11                 t2 = time.time()
    12                 print("t: %r " % (t2 - t1))
    13                 self.function_dict[route] = fn.__name__
    14                 return outcome
    15             return fn
    16         return decorator
    17     
    18 app = Add("apple")
    19 
    20 @app.add("/index")
    21 def add(var1, var2):
    22     return var1 * var2
    23 print(add(4, 5))
    24 print(app.function_dict)
    View Code

        打印结果为:

    1 t: 1.9073486328125e-06 
    2 20
    3 {'/index': 'add'}
    View Code

    四、列表生成式、匿名函数和高阶函数

      1、列表生成式[列表推导式]

        列表生成式是可迭代对象迭代时的一种简便写法。

     1 table = [
     2     {"name": "baiqi", "attr": "guy", "rank": 1},
     3     {"name": "limu", "attr": "guy", "rank": 2},
     4     {"name": "wangjian", "attr": "guy", "rank": 3},
     5     {"name": "lianpo", "attr": "guy", "rank": 4},
     6     {"name": "xishi", "attr": "girl", "rank": 1},
     7     {"name": "wangzhaojun", "attr": "girl", "rank": 2},
     8     {"name": "diaochan", "attr": "girl", "rank": 3},
     9     {"name": "yangyuhuan", "attr": "girl", "rank": 3},
    10 ]
    11 
    12 
    13 name = [person["name"] for person in table]
    14 
    15 name = [[person["name"], person["attr"], person["rank"]] for _, person in enumerate(table)]
    16 name = [{person["name"]: person["rank"]} for _, person in enumerate(table)]
    17 
    18 name = {person["name"]: [person["attr"], person["rank"]] for _, person in enumerate(table)}
    19 
    20 boy = [person["name"] for person in table if person["attr"] == "guy"]
    21 boy = [person["name"] for person in table if person["rank"] >= 3]
    22 
    23 girl = [person["name"] if person["rank"] >3 and person["attr"] == "girl" else -1 for person in table]
    View Code

      实际上,列表生成式的使用要比上面的例子中更灵活。比如两层循环或者两个可变序列情形时。

      2、匿名函数

        对于上面的第一个name,可以用lambda函数来重写。同样地,可以用lambda改写上面所有的列表生成式。lambda还支持传入函数,但是没有必要性,它的核心在于简洁。

        lambda params: expression。

    1 func = lambda lis: [person["name"] for person in lis]
    2 func(table)
    View Code

      3、三个高阶函数

        高阶指的是该函数的作用域和存在的名称空间,以及它替代一些逻辑代码块的作用。

        apply(FUNCTION, TUPLE)。快要被摒弃的函数。reduce(),逐步叠加计算,在python3中已不存在了。

        map(FUNCTION),遍历,function作用在序列的每个元素上以改变元素。filter(FUNCTION),过滤,function作用在序列的每个元素上,进行条件判断并返回符合条件的元素。这些函数都不改变原序列。

    1 number = list(range(1, 11, 2))
    2 outcome1 = list(map(lambda x: x**2, number))
    3 outcome2 = list(filter(lambda x: x>4, number))
    4 print(outcome1)
    5 print(outcome2)
    View Code

        需要注意的是,map函数和filter函数的返回结果是map对象和filter对象,需要list转换一下。这些高阶函数apply函数和map函数也会以方法的形式出现在一些第三方模块中。

     1 import pandas as pd
     2 df = pd.DataFrame(table)
     3 df["name_map"] = df.name.map(lambda x: x + "_China")
     4 print(df)
     5 # 打印结果为:
     6 
     7     attr          name  rank            name_map
     8 0    guy         baiqi     1         baiqi_China
     9 1    guy          limu     2          limu_China
    10 2    guy      wangjian     3      wangjian_China
    11 3    guy        lianpo     4        lianpo_China
    12 4   girl         xishi     1         xishi_China
    13 5   girl   wangzhaojun     2   wangzhaojun_China
    14 6   girl      diaochan     3      diaochan_China
    View Code

      4、偏函数

        偏函数的功能和上下文管理器一致。都是管理上下文的开始和结束,即with ... 直到结束。在tensorflow及其它的一些框架中,reader()这个阅读器的功能随处可见,可见它功能还是很强大的。

     1 f1 = open("homework/user.txt", mode="r", encoding="utf-8")
     2 data = f1.readlines()
     3 f1.close()
     4 
     5 with open("homework/user.txt", mode="r", encoding="utf-8") as f3:
     6     data = f3.readlines()
     7 
     8 import functools
     9 reader = functools.partial(open, mode="r", encoding="utf-8")
    10 f3 = reader("homework/user.txt")
    11 f3.readlines()
    View Code

      5.递归函数

        在函数的内部调用自己本身的函数称为递归函数,递归的最大深度为998。两个例子:

    1 # 利用递归函数求阶乘
    2 def factorial(number, start=1):
    3     fac = 1 if start in [0, 1] else start
    4     if start < int(number):
    5         return factorial(number, start+1) * fac
    6     else:
    7         return fac
    View Code

        注意,函数内部的factorial(number, start+1)就相当于fac。这是它的返回结果,递归函数必须存在可以终止的return。另一个特性,递归函数会在当件未满足时一直递归。

     1 # 二分查找
     2 def func(value, l, index=0):
     3     """返回被查找值的索引"""
     4     middle = len(l) // 2  # 切片
     5     if middle == 0:  # 当middle为0时,说明len(l)的值<=1
     6         return 0 if value == l[0] else None
     7     if value > l[middle]:
     8         index += middle
     9         return func(value, l[middle:], index)
    10     elif value < l[middle]:
    11         return func(value, l[: middle], index)
    12     else:
    13         return l.index(value) + index
    14 
    15 l = [2,3,5,10,15,16,18,22,26,30,32,35,41,42,43,55,56,66,67,69,72,76,82,83,88, 100]
    16 print(func(2, l))
    17 print(func(100, l))
    18 print(func(14, l))
    19 # 打印结果为
    20 # 0
    21 # 25
    22 # None
    View Code

      6.内置函数

        python3.6.4官方文档中给出的内置函数如下。

        

  • 相关阅读:
    JDK历史版本下载
    安装cocoapods及相关问题解决
    使用spring注解@Controller @Service @Repository简化配置
    自己动手编写spring IOC源码
    利用反射手写代码实现spring AOP
    浅谈系统架构<一>
    spring 声明式事务管理详解
    eclipse中SSH三大框架环境搭建<二>
    eclipse中SSH三大框架环境搭建<三>
    轻松理解spring IOC
  • 原文地址:https://www.cnblogs.com/kuaizifeng/p/8876619.html
Copyright © 2011-2022 走看看