Python笔记(二) -- 基础语法

一、 函数

1.1 匿名函数(lambda函数)

lambda是一个表达式,用来编写简单的函数,返回一个值。  格式:   [  lambda 参数 : 表达式  ]

fun_lambda = lambda x: x ** 2
print(fun_lambda(5))              # 25

1.2 partial函数

函数在执行时要带上必要的参数进行调用。然而有时在函数调用前有部分参数已经提前确定。这种情况下可以定义partial function从而减少函数的参数。

python提供了partial函数用于携带部分参数生成一个新函数,但partial函数不会带来运行效率的提高,只会使代码更简洁。

import functools

def add(a, b):
    return a + b

# 定义partial函数plus3
plus3 = functools.partial(add, 3)
print(plus3(7))

1.3 函数参数

函数参数分为位置参数默认参数可变参数关键字参数命名关键字参数

1.3.1 位置参数

# a和b是位置参数
def add(a, b):
   return a+b

1.3.2 默认参数

  • 注意位置参数在前,默认参数在后
  • 注意: 默认参数必须指向不变对象,不能指向list之类的可变对象。eg: def add_end(L=[])是错误的
# name和gender是位置参数,age和city是默认参数
def info(name, gender, age=6, city='Beijing'):
    return name,gender,age,city

# 按顺序提供默认参数
print(info('bacon','M',18))                       # ('bacon', 'M', 18, 'Beijing')
print(info('bacon','M',18,'Guangzhou'))           # ('bacon', 'M', 18, 'Guangzhou')
# 使用参数名方式提供默认方式
print(info('bacon','M',city='Guangzhou',age=18))  # ('bacon', 'M', 18, 'Guangzhou')
# 错误示范
def add_end(L=[]):
    L.append('END')
    return L

print(add_end())  # ['END']
print(add_end())  # ['END', 'END']

'''
解释:
    Python函数在定义的时候,默认参数L的值就被计算出来了,即[],因为默认参数L也是一个变量,
    它指向对象[],每次调用该函数,如果改变了L的内容,则下次调用时,默认参数的内容就变了,
    不再是函数定义时的[]了。
'''

# 改正
def add_end(L=None):
    if L is None:
        L = []
    L.append('END')
    return L

 1.3.3 可变参数

可变参数就是传入的参数个数是可变的。可变参数允许你传入0个或任意个参数,这些可变参数在函数调用时自动组装为一个tuple

def calc(*numbers):
    sum = 0
    for n in numbers:
        sum = sum + n
    return sum

print(calc(1, 2))         # 3
print(calc(1, 3, 5, 7))   # 16
print(calc(*[1,2,3,4]))   # 10
print(calc(*(1,2,3,4)))   # 10

1.3.4 关键字参数

关键字参数允许你传入0个或任意个含参数名的参数,这些关键字参数在函数内部自动组装为一个dict。

def person(name, age, **kw):
    if 'city' in kw:
        # 有city参数
        pass
    if 'job' in kw:
        # 有job参数
        pass
    print('name:', name, 'age:', age, 'other:', kw)
    
person('Michael', 30)                       # name: Michael age: 30 other: {}
person('Bob', 35, city='Beijing')           # name: Bob age: 35 other: {'city': 'Beijing'}
person('Adam', 45, gender='M', job='AI')    # name: Adam age: 45 other: {'gender': 'M', 'job': 'AI'}
extra = {'city': 'Beijing', 'job': 'Engineer'}
person('Jack', 24, **extra)                 # name: Jack age: 24 other: {'city': 'Beijing', 'job': 'Engineer'}

1.3.5 命名关键字参数 

  • 对于关键字参数,函数的调用者可以传入任意不受限制的关键字参数。至于到底传入了哪些,就需要在函数内部通过kw检查。
  • 如果要限制关键字参数的名字,就可以用命名关键字参数。命名关键字参数需要一个特殊分隔符**后面的参数被视为命名关键字参数。命名关键字参数必须传入参数名。
  • 命名关键字参数可以有缺省值,从而简化调用
# 1
def person(name, age, *, city, job):
    print(name, age, city, job)

person('Jack', 24, city='Beijing', job='Engineer') # Jack 24 Beijing Engineer
person('Jack', 24, city='Beijing') # TypeError: person() missing 1 required keyword-only argument: 'job'

# 2 如果函数定义中已经有了一个可变参数,后面跟着的命名关键字参数就不再需要一个特殊分隔符*了
def person(name, age, *args, city='Beijing', job):
    print(name, age, args, city, job)

person('Jack', 24,1,2,3, city='Beijing',job='AI') # Jack 24 (1, 2, 3) Beijing AI

1.3.6  函数参数总结

在Python中定义函数,可以用必选参数(位置参数)默认参数可变参数关键字参数命名关键字参数,这5种参数都可以组合使用。但是请注意,参数定义的顺序必须是:必选参数(位置参数)、默认参数、可变参数、命名关键字参数和关键字参数

对于任意函数,都可以通过类似func(*args, **kw)的形式调用它,无论它的参数是如何定义的。

# 函数参数综合示例
def f1(a, b, c=0, *args, **kw):
    print('a =', a, 'b =', b, 'c =', c, 'args =', args, 'kw =', kw)

def f2(a, b, c=0, *, d, **kw):
    print('a =', a, 'b =', b, 'c =', c, 'd =', d, 'kw =', kw)

f1(1, 2)                       # a = 1 b = 2 c = 0 args = () kw = {}
f1(1, 2, c=3)                  # a = 1 b = 2 c = 3 args = () kw = {}
f1(1, 2, 3, 'a', 'b')          # a = 1 b = 2 c = 3 args = ('a', 'b') kw = {}
f1(1, 2, 3, 'a', 'b', x=99)    # a = 1 b = 2 c = 3 args = ('a', 'b') kw = {'x': 99}
f2(1, 2, d=99, ext=None)       # a = 1 b = 2 c = 0 d = 99 kw = {'ext': None}

args = (1, 2, 3, 4)
kw = {'d': 99, 'x': '#'}
f1(*args, **kw)                # a = 1 b = 2 c = 3 args = (4,) kw = {'d': 99, 'x': '#'}

args = (1, 2, 3)
kw = {'d': 88, 'x': '#'}
f2(*args, **kw)                # a = 1 b = 2 c = 3 d = 88 kw = {'x': '#'}

 

二、字符串编解码问题

  • 计算机只能处理数字。
  • 如果要处理文本,就必须先把文本转换成数字。即 ‘文本’ 编码成  '数字',例如把 'A' 编码成 65.
  • 编码表:把文本转换成数字的规则表。例如早期的ASCII编码表编码了127个字符。全世界有上百种语言,中国制定了GB2312编码,用来把中文编进去;日本把日文编到Shift_JIS里;韩国把韩文编到Euc-kr里;各国有各国的标准,所以会不可避免的出现冲突。在多语言混合的文本中,显示出来会有乱码。
  • Unicode把所有语言都统一到一套编码里,这样就不会出现乱码问题。常用的是用两个字节表示一个字符(如果要用到非常偏僻的字符,就需要4个字节)。但是,如果你写的文本基本上全部是英文的话,用Unicode编码比ASCII编码需要多一倍的存储空间,在存储和传输上就十分不划算。
  • 本着节约的精神,又出现了把Unicode编码转化为“可变长编码”的UTF-8编码。UTF-8编码把一个Unicode字符根据不同的数字大小编码成1-6个字节,常用的英文字母被编码成1个字节,汉字通常是3个字节,只有很生僻的字符才会被编码成4-6个字节。如果你要传输的文本包含大量英文字符,用UTF-8编码就能节省空间。UTF-8编码有一个额外的好处,就是ASCII编码实际上可以被看成是UTF-8编码的一部分,所以,大量只支持ASCII编码的历史遗留软件可以在UTF-8编码下继续工作。
字符ASCIIUnicodeUTF-8
A0100000100000000 0100000101000001
x01001110 0010110111100100 10111000 10101101
  • 在计算机内存中,统一使用Unicode编码(python的str数据类型,一个字符对应若干个字节,eg: "abc")。
  • 保存到硬盘或者需要传输的时候,就转换为UTF-8编码(byte,以字节为单位, eg: b"abc")。
  • 例如,用记事本编辑的时候,从文件读取的UTF-8字符被转换为Unicode字符加载到内存里(decode: byte -> str);编辑完成后,保存的时候再把Unicode转换为UTF-8保存到文件(encode: str -> byte)
# ord()函数获取字符的整数表示
print(ord('A'))                    # 65
print(ord("中"))                   # 20013
# chr()函数把编码转换为对应的字符
print(chr(66))                     # B 
print(chr(20050))                  # 乒
# 编码encode: str -> byte
print('ABC'.encode('ascii'))   # b'ABC'
#  在bytes中,无法显示为ASCII字符的字节,用\x##显示。
print('中文'.encode('utf-8'))  # b'\xe4\xb8\xad\xe6\x96\x87'
# 解码decode: byte -> str
print(b'ABC'.decode('ascii'))                                # ABC
print(b'\xe4\xb8\xad\xe6\x96\x87'.decode('utf-8'))           # 中文

# 如果bytes中包含无法解码的字节,decode()方法会报错
# 如果bytes中只有一小部分无效的字节,可以传入errors='ignore'忽略错误的字节
print(b'\xe4\xb8\xad\xff'.decode('utf-8', errors='ignore'))  # 中
# len()函数计算的是str的字符数,如果换成bytes,len()函数就计算字节数
print(len('ABC'))                    # 3
print(len(b'ABC'))                   # 3
print(len('中文'))                   # 2
print(len('中文'.encode('utf-8')))   # 6 # utf-8一个中文用三个字节编码
  • 在操作字符串时,我们经常遇到strbytes的互相转换。为了避免乱码问题,应当始终坚持使用UTF-8编码对strbytes进行转换。
  • Python源代码[xxx.py]也是一个文本文件,所以,当你的源代码中包含中文的时候,在保存源代码时,就需要务必指定保存为UTF-8编码。
  • 当Python解释器读取源代码时,为了让它按UTF-8编码读取,我们通常在文件开头写上 " # coding = utf8 "

 

三、 可迭代对象(Iterable),生成器(generator),迭代器(Iterator)

不但可以作用于for循环,还可以被next()函数调用并不断返回下一个值的对象称为迭代器:Iterator

3.1 可迭代对象

  • 可以通过for循环来遍历list或tuple等,这种遍历称为迭代:Iteration
  • 可以直接作用于for循环的对象统称为可迭代对象:Iterable
d = {'a': 1, 'b': 2, 'c': 3}
for key in d:                # 迭代key
for value in d.values()      # 迭代value
for k, v in d.items()        # 同时迭代key和value

 通过collections模块的Iterable类型来判断一个对象是否是可迭代对象

from collections import Iterable
isinstance('abc', Iterable)     # True
isinstance([1,2,3], Iterable)   # True
isinstance(123, Iterable)       # False

 如果要对list实现下标循环,Python内置的enumerate函数可以把一个list变成索引-元素对,这样就可以在for循环中同时迭代索引和元素本身。

for i, value in enumerate(['A', 'B', 'C']):
    print(i, value)

'''
0 A
1 B
2 C
'''

3.2 生成器

  • 生成式是Python内置的非常简单却强大的,可以用来创建相关数据结构的生成式。
  • 列表生成器,字典生成器,集合生成器
[x * x for x in range(1, 11) if x % 2 == 0]     # [4, 16, 36, 64, 100]
{x: x ** 2 for x in range(1, 5)}                # {1: 1, 2: 4, 3: 9, 4: 16}
{x for x in range(1, 5)}                        # {1, 2, 3, 4}
  • 通过列表生成式可以直接创建一个列表。但是如果要创建一个包含100万个元素的列表,受到内存限制,列表容量肯定是有限的。如果列表元素可以按照某种算法推算出来,就不必创建完整的list,从而节省大量的空间。
  • 在Python中,这种一边循环一边计算的机制,称为生成器:generator
  • 创建一个generator有很多种方法

         1)把一个列表生成式的[]改成()  .

         2)如果一个函数定义中包含yield关键字,那么这个函数就不再是一个普通函数,而是一个generator.

#  1)把一个列表生成式的[]改成()
g = (x * x for x in range(3))
# 通过next()函数获得generator的下一个返回值
# 直到计算到最后一个元素,没有更多的元素时,抛出StopIteration的错误。
print(next(g))                 # 0
print(next(g))                 # 1
print(next(g))                 # 4
print(next(g))                 # 崩溃,StopIteration
g = (x * x for x in range(3))
# 使用for循环遍历生成器,因为generator也是可迭代对象
for n in g:
    print(n)

函数是顺序执行,遇到return语句或者最后一行函数语句就返回。而变成generator的函数,在每次调用next()的时候执行,遇到yield语句返回,再次执行时从上次返回的yield语句处继续执行。 

#  2)如果一个函数定义中包含yield关键字,那么这个函数就不再是一个普通函数,而是一个generator.
def fib(max):
    n, a, b = 0, 0, 1
    while n < max:
        yield b            # 把print改成yield
        a, b = b, a + b
        n = n + 1
    return 'done'

g=fib(5)

for v in g:
    print(v)               # 1 1 2 3 5

3.3 迭代器

可以被next()函数调用并不断返回下一个值的对象称为迭代器:Iterator。生成器都是Iterator对象。可以使用isinstance()判断一个对象是否是Iterator对象。

listdictstr虽然是Iterable,却不是Iterator。把listdictstrIterable变成Iterator可以使用iter()函数。

from collections import Iterator
isinstance((x for x in range(10)), Iterator)   # True
isinstance(iter([]), Iterator)       # True
isinstance(iter('abc'), Iterator)    # True

 

四、函数式编程

函数是面向过程的程序设计的基本单元。而函数式编程也可以归结到面向过程的程序设计,但其思想更接近数学计算。函数式编程的一个特点就是,允许把函数本身作为参数传入另一个函数,还允许返回一个函数。

4.1 高阶函数

一个函数接收另一个函数作为参数,这种函数就称之为高阶函数。

map()函数

map()函数接收两个参数,一个是函数,一个是Iterablemap将传入的函数依次作用到序列的每个元素,并把结果作为新的Iterator返回。

def f(x):
    return x * x

re = map(f, [1, 2, 3, 4, 5])
print(list(re))                   # [1, 4, 9, 16, 25]

 reduce()函数

reduce把一个函数作用在一个序列[x1, x2, x3, ...]上,这个函数必须接收两个参数,reduce把结果继续和序列的下一个元素做累积计算。等价于

reduce(f, [x1, x2, x3, x4]) = f(f(f(x1, x2), x3), x4)
# 把序列[1, 3, 5, 7, 9]变换成整数13579

from functools import reduce
def fn(x, y):
    return x * 10 + y

print(reduce(fn, [1, 3, 5, 7, 9])) # 13579
# 结合map和reduce实现str转int
from functools import reduce

DIGITS = {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9}

def char2num(s):
    return DIGITS[s]

def str2int(s):
    return reduce(lambda x, y: x * 10 + y, map(char2num, s))

filter()函数

Python内建的filter()函数用于过滤序列。filter()也接收一个函数和一个序列,把传入的函数依次作用于每个元素,然后根据返回值是True还是False决定保留还是丢弃该元素。

# 在一个list中,删掉偶数,只保留奇数
def is_odd(n):
    return n % 2 == 1

print(list(filter(is_odd, [1, 2, 4, 5, 6, 9, 10, 15])))  # [1, 5, 9, 15]
# 把一个序列中的空字符串删掉
def not_empty(s):
    return s and s.strip()

print(list(filter(not_empty, ['A', '', 'B', None, 'C', '  ']))) # ['A', 'B', 'C']
# 打印1000以内的素数
# 1. 构造一个从3开始的奇数序列。注意这是一个生成器,并且是一个无限序列
def _odd_iter():
    n = 1
    while True:
        n = n + 2
        yield n

# 2. 定义一个筛选函数
def _not_divisible(n):
    return lambda x: x % n > 0

# 3. 定义一个生成器,不断返回下一个素数
def primes():
    yield 2
    it = _odd_iter() # 初始序列
    while True:
        n = next(it) # 返回序列的第一个数
        yield n
        it = filter(_not_divisible(n), it) # 构造新序列

for n in primes():
    if n < 1000:
        print(n)
    else:
        break

 sorted函数

Python内置的sorted()函数就可以对list进行排序。sorted()函数也是一个高阶函数,它还可以接收一个key函数来实现自定义的排序。

sorted()排序的关键在于实现一个映射函数。

# 按绝对值大小排序
print(sorted([36, 5, -12, 9, -21], key=abs))  # [5, 9, -12, -21, 36]

'''
keys排序结果 => [5, 9,  12,  21, 36]
                |  |    |    |   |
最终结果     => [5, 9, -12, -21, 36]
'''
# 小写反向排序
print(sorted(['bob', 'about', 'Zoo', 'Credit'], key=str.lower, reverse=True)) 
# ['Zoo', 'Credit', 'bob', 'about']

4.2 返回函数 

def lazy_sum(*args):
    def sum():
        ax = 0
        for n in args:
            ax = ax + n
        return ax
    return sum

# 当调用lazy_sum()时,返回的并不是求和结果,而是求和函数。
# 当lazy_sum返回函数sum时,相关参数和变量都保存在返回的函数中,这种程序结构称为“闭包(Closure)”。
lazy_sum(1, 3, 5, 7, 9)()  # 25

4.3 装饰器

在代码运行期间动态增加功能的方式,称之为“装饰器”(Decorator)。本质上,decorator就是一个返回函数的高阶函数。

# 定义一个能打印日志的decorator
def log(func):
    def wrapper(*args, **kw):
        print('call %s():' % func.__name__)
        return func(*args, **kw)
    return wrapper

# 把@log放到now()函数的定义处,相当于执行了语句 now = log(now)
@log
def now():
    print("now")
    
now()

'''
call now():
now
'''

 由于log()是一个decorator,返回一个函数,所以,原来的now()函数仍然存在,只是现在同名的now变量指向了新的函数,于是调用now()将执行新函数,即在log()函数中返回的wrapper()函数。

wrapper()函数的参数定义是(*args, **kw),因此,wrapper()函数可以接受任意参数的调用。在wrapper()函数内,首先打印日志,再紧接着调用原始函数。

在面向对象(OOP)的设计模式中,decorator被称为装饰模式。OOP的装饰模式需要通过继承和组合来实现,而Python除了能支持OOP的decorator外,直接从语法层次支持decorator。Python的decorator可以用函数实现,也可以用类实现。decorator可以增强函数的功能,定义起来虽然有点复杂,但使用起来非常灵活和方便。

 

小知识点汇总

1. 直接运行.py文件

 在Mac和Linux上可以像.exe文件那样直接运行.py文件(在Windows中不行),只需在文件开头定义如下代码。

#!/usr/bin/env python3

2. 输入 

name = input("please input name:")  # 注意接受的都是str类型
print(name)

3. 动态语言和静态语言

变量本身类型不固定的语言称之为动态语言,与之对应的是静态语言。静态语言在定义变量时必须指定变量类型,如果赋值的时候类型不匹配,就会报错。

a = 'ABC'
b = a
a = 'XYZ'
print(b)   # 'ABC'

4. 使用isinstance()进行数据类型检查

# 使用isinstance()进行数据类型检查
def my_abs(x):
    if not isinstance(x, (int, float)):
        raise TypeError('bad operand type')
    if x >= 0:
        return x
    else:
        return -x
    
my_abs("1")  # TypeError: bad operand type

5. 包和模块

  • 一个.py文件就称之为一个模块(Module)
  • 每个文件夹就是一个包。每一个包目录下面都会有一个__init__.py的文件,这个文件是必须存在的,否则,Python就把这个目录当成普通目录,而不是一个包。

6. python支持 x+=1,但不支持 x++。

 

参考

 

 

 

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值