韩非囚秦
——独善其身者,难成大事也。

导航

 

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

  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官方文档中给出的内置函数如下。

    

posted on 2018-04-19 00:30  一只火眼金睛的男猴  阅读(636)  评论(0编辑  收藏  举报