Python

Python 虚拟环境 venv

创建虚拟环境

py -3 # python3 ?

py -3 -m venv [path]

example
py -3 -m venv venv

# 进入 \退出
venv\Scripts\activate    ---> deactivate

Notes

  • python 解释型编程,弱类型语言,可以重复赋值
  • 浮点数存在的精度问题
  • 数据类型 type()
  • 这是因为input(),返回的数据类型是str

数据类型

  a = 2
  type(a)
  class<'int'>
  • a / b (float) | a // b (int) (取整, 整数时候, 浮点数 // 还是 浮点), 还有一种除法是 //,称为地板除
  • %
10 % 3
>>> 1
10 % 3.0
>>> 1.0
  • Python 允许在数字中间以_分隔,因此,写成 10_000_000_000 和 10000000000 是完全一样的

  • 布尔值可以用 and、or 和 not 运算 , not 运算是非运算,它是一个单目运算符,把 True 变成 False,False 变成 True 类似 JavaScript 的 (&& ,|| ,!)

  • 空值是 Python 里一个特殊的值,用 None 表示。None 不能理解为 0,因为 0 是有意义的,而 None 是一个特殊的空值, 也不是空字符串

  • Python 的整数没有大小限制,而某些语言的整数根据其存储长度是有大小限制的,例如 Java 对 32 位整数的范围限制在-2147483648-2147483647,Python 的浮点数也没有大小限制,但是超出一定范围就直接表示为 inf(无限大)

  • 由于计算机是美国人发明的,因此,最早只有 127 个字符被编码到计算机里,也就是大小写英文字母、数字和一些符号,这个编码表被称为 ASCII 编码,比如大写字母 A 的编码是 65,小写字母 z 的编码是 122。但是要处理中文显然一个字节是不够的,至少需要两个字节,而且还不能和 ASCII 编码冲突,所以,中国制定了 GB2312 编码,用来把中文编进去

  • Unicode 字符集应运而生。Unicode 把所有语言都统一到一套编码里,这样就不会再有乱码问题了

  • 如果统一成 Unicode 编码,乱码问题从此消失了。但是,如果你写的文本基本上全部是英文的话,用 Unicode 编码比 ASCII 编码需要多一倍的存储空间,在存储和传输上就十分不划算 -> utf-8

  • UTF-8 编码把一个 Unicode 字符根据不同的数字大小编码成 1-6 个字节,常用的英文字母被编码成 1 个字节,汉字通常是 3 个字节,只有很生僻的字符才会被编码成 4-6 个字节

  • 由于 Python 源代码也是一个文本文件,所以,当你的源代码中包含中文的时候,在保存源代码时,就需要务必指定保存为 UTF-8 编码。当 Python 解释器读取源代码时,为了让它按 UTF-8 编码读取,我们通常在文件开头写上这两行

    # !/usr/bin/env python3
    # -*- coding: utf-8 -*-
    
  • 占位符,如果你不太确定应该用什么,%s永远起作用,它会把任何数据类型转换为字符串。有些时候,字符串里面的%是一个普通字符怎么办?这个时候就需要转义,用%%来表示一个% %d 整数 %f 浮点数 %s 字符串 %x 十六进制整数 `python 'Hi, %s, you have $%d.' % ('Michael', 1000000)

    'Hi, Michael, you have $1000000.' #最后一种格式化字符串的方法是使用以 f 开头的字符串,称之为 f-string,它和普通字符串不同之处在于,字符串如果包含{xxx},就会以对应的变量替换 r = 2.5 s = 3.14 * r ** 2 print(f'The area of a circle with radius {r} is {s:.2f}') The area of a circle with radius 2.5 is 19.62

    • :后面的.2f 指定了格式化参数(即保留两位小数)

    `

  • 列表如果要取最后一个元素,除了计算索引位置外,还可以用-1做索引,直接获取最后一个元素,以此类推,可以获取倒数第 2 个、倒数第 3 个

    list.append()
    list.insert(1, 'Jack') #把元素插入到指定的位置,比如索引号为1的位置
    list.pop() #要删除list末尾的元素
    
  • 元组(有序列表):tuple。tuple 和 list 非常类似,但是 tuple 一旦初始化就不能修改,比如同样是列出同学的名字

    classmates = ('Michael', 'Bob', 'Tracy')
    
    #tuple的陷阱:当你定义一个tuple时,在定义的时候,tuple的元素就必须被确定下来
    t = (1,2)
    >>> 1,2
    
    t = ()
    >>> ()
    
    t = (1)
    >>> 1 #定义的不是tuple,是1这个数!这是因为括号()既可以表示tuple,又可以表示数学公式中的小括号,这就产生了歧义,因此,Python规定,这种情况下,按小括号进行计算,计算结果自然是1
    
    t = (1,)
    >>> (1,)
    

条件判断 \ 循环

  • if <条件判断1>:
        <执行1>
    elif <条件判断2>:
        <执行2>
    elif <条件判断3>:
        <执行3>
    else:
        <执行4>
    
    # for
    >>>for name in names:
    
    # while
    >>>while n > 0:
    
    

dict \ set

  • # dist
    # dict全称dictionary,在其他语言中也称为map,使用键-值(key-value)存储,具有极快的查找速度
    >>>d = {'Michael': 95, 'Bob': 75, 'Tracy': 85}
    >>>d['Michael']
    95
    # 如果key不存在,dict就会报错, 避免key不存在的错误,有两种办法,
    # 一是通过in判断key是否存在
    >>>'Thomas' in d
    # 二是通过dict提供的get()方法,如果key不存在,可以返回None,或者自己指定的value
    >>>d.get('Thomas', -1)
    -1
    # 要删除一个key,用pop(key)方法,对应的value也会从dict中删除
    
    # set
    # set和dict类似,也是一组key的集合,但不存储value。由于key不能重复,所以,在set中,没有重复的key,重复元素在set中自动被过滤
    >>>s = set([1, 1, 2, 2, 3, 3])
    {1,2,3}
    # 通过add(key)方法可以添加元素到set中,可以重复添加,但不会有效果,通过remove(key)方法可以删除元素
    #两个set可以做数学意义上的交集、并集等操作
    >>> s1 = set([1, 2, 3])
    >>> s2 = set([2, 3, 4])
    >>> s1 & s2
    {2, 3}
    >>> s1 | s2
    {1, 2, 3, 4}
    
    

函数

内置函数open in new window

空函数

# 想定义一个什么事也不做的空函数,可以用pass语句,pass可以用来作为占位符,比如现在还没想好怎么写函数的代码,就可以先放一个pass,让代码能运行起来

Notes

  • 数据类型检查可以用内置函数isinstance()实现

    if not isinstance(x, (int, float)):
            raise TypeError('bad operand type')
    
  • 默认参数**def** **power**(x, n=2)

  • 定义默认参数要牢记一点:默认参数必须指向不变对象!

  • 可变参数**def** **calc**(\*numbers): 在函数内部,参数numbers接收到的是一个 tuple

  • Python 允许你在 list 或 tuple 前面加一个*号,把 list 或 tuple 的元素变成可变参数传进去

    >>> nums = [1, 2, 3]
    >>> calc(*nums)
    14
    
  • 关键字参数

    # 关键字参数有什么用?它可以扩展函数的功能。比如,在person函数里,我们保证能接收到name和age这两个参数,但是,如果调用者愿意提供更多的参数,我们也能收到。试想你正在做一个用户注册的功能,除了用户名和年龄是必填项外,其他都是可选项,利用关键字参数来定义这个函数就能满足注册的需求
    def person(name, age, **kw):
        print('name:', name, 'age:', age, 'other:', kw)
    
    # 如果要限制关键字参数的名字,就可以用命名关键字参数,例如,只接收city和job作为关键字参数。这种方式定义的函数如下
    def person(name, age, *, city, job):
        print(name, age, city, job)
    # 和关键字参数**kw不同,命名关键字参数需要一个特殊分隔符*,*后面的参数被视为命名关键字参数
    
    # 参数定义的顺序必须是:必选参数、默认参数、可变参数、命名关键字参数和关键字参数
    
  • 切片

    # L[0:3]表示,从索引0开始取,直到索引3为止,但不包括索引3。即索引0,1,2,正好是3个元素
    # 如果第一个索引是0,还可以省略,支持倒数切片
    >>> L[-2:]
    ['Bob', 'Jack']
    >>> L[-2:-1]
    ['Bob']
    
    # 前10个数,每两个取一个
    >>> L[:10:2]
    [0, 2, 4, 6, 8]
    # 所有数,每5个取一个:
    >>> L[::5]
    [0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75, 80, 85, 90, 95]
    # 甚至什么都不写,只写[:]就可以原样复制一个list
    >>> L[:]
    [0, 1, 2, 3, ..., 99]
    
    # 字符串'xxx'也可以看成是一种list,每个元素就是一个字符。因此,字符串也可以用切片操作,只是操作结果仍是字符串
    >>> 'ABCDEFG'[:3]
    'ABC'
    >>> 'ABCDEFG'[::2]
    'ACEG'
    
  • 迭代 | enumerate 函数可以把一个list变成索引-元素对

    # for循环不仅可以用在list或tuple上,还可以作用在其他可迭代对象上, 字符串也是可迭代对象
    # 只要是可迭代对象,无论有无下标,都可以迭代,比如dict就可以迭代
    # 默认情况下,dict迭代的是key。如果要迭代value,可以用for value in d.values(),如果要同时迭代key和value,可以用for k, v in d.items()
    
    # 如何判断一个对象是可迭代对象呢?方法是通过collections.abc模块的Iterable类型判断
    >>> from collections.abc import Iterable
    >>> isinstance('abc', Iterable) # str是否可迭代
    True
    >>> isinstance([1,2,3], Iterable) # list是否可迭代
    True
    >>> isinstance(123, Iterable) # 整数是否可迭代
    False
    
  • 列表生成式

    # 要生成list [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]可以用list(range(1, 11))
    
    # **[x * x for x in range(1, 11) if x % 2 == 0]
    
    # 可以使用两层循环,可以生成全排列
    >>> [m + n for m in 'ABC' for n in 'XYZ']
    ['AX', 'AY', 'AZ', 'BX', 'BY', 'BZ', 'CX', 'CY', 'CZ']**
    
  • generator

    # 这种一边循环一边计算的机制,称为生成器:generator
    # generator保存的是算法,每次调用next(g),就计算出g的下一个元素的值,直到计算到最后一个元素,没有更多的元素时,抛出StopIteration的错误。当然,上面这种不断调用next(g)实在是太变态了,正确的方法是使用for循环,因为generator也是可迭代对象
    
    #我们创建了一个generator后,基本上永远不会调用next(),而是通过for循环来迭代它,并且不需要关心StopIteration的错误。generator非常强大。如果推算的算法比较复杂,用类似列表生成式的for循环无法实现的时候,还可以用函数来实现
    
    #著名的斐波拉契数列(Fibonacci),除第一个和第二个数外,任意一个数都可由前两个数相加得到
    def fib(max):
        n, a, b = 0, 0, 1
        while n < max:
            print(b)
            a, b = b, a + b
            n = n + 1
        return 'done'
    >>> fib(6)
    1
    1
    2
    3
    5
    8
    'done'
    
    # 这就是定义generator的另一种方法。如果一个函数定义中包含yield关键字,那么这个函数就不再是一个普通函数,而是一个generator函数,调用一个generator函数将返回一个generator
    

这些可以直接作用于for循环的对象统称为可迭代对象:Iterable。可以使用isinstance()判断一个对象是否是Iterable对象

可以被next()函数调用并不断返回下一个值的对象称为迭代器:Iterator

生成器都是Iterator对象,但listdictstr虽然是Iterable,却不是Iterator

  • map \ reduce
# map()函数接收两个参数,一个是函数,一个是Iterable,map将传入的函数依次作用到序列的每个元素,并把结果作为新的Iterator返回
>>> def f(x):
...     return x * x
...
>>> r = map(f, [1, 2, 3, 4, 5, 6, 7, 8, 9])
>>> list(r)
[1, 4, 9, 16, 25, 36, 49, 64, 81]

# reduce把一个函数作用在一个序列[x1, x2, x3, ...]上,这个函数必须接收两个参数,reduce把结果继续和序列的下一个元素做累积计算,reduce(f, [x1, x2, x3, x4]) = f(f(f(x1, x2), x3), x4)
>>> from functools import reduce
>>> def add(x, y):
...     return x + y
...
>>> reduce(add, [1, 3, 5, 7, 9])
25
  • 装饰器
# decorator就是一个返回函数的高阶函数。所以,我们要定义一个能打印日志的decorator
def log(func):
    def wrapper(*args, **kw):
        print('call %s():' % func.__name__)
        return func(*args, **kw)
    return wrapper

@log
def now():
    print('2015-3-25')

>>> now()
call now():
2015-3-25
# 把@log放到now()函数的定义处,相当于执行了语句
now = log(now)

#
def log(text):
    def decorator(func):
        def wrapper(*args, **kw):
            print('%s %s():' % (text, func.__name__))
            return func(*args, **kw)
        return wrapper
    return decorator

@log('execute')
def now():
    print('2015-3-25')

# 因为返回的那个wrapper()函数名字就是'wrapper',所以,需要把原始函数的__name__等属性复制到wrapper()函数中,否则,有些依赖函数签名的代码执行就会出错。不需要编写wrapper.__name__ = func.__name__这样的代码,Python内置的functools.wraps就是干这个事的

import functools

def log(func):
    @functools.wraps(func)
    def wrapper(*args, **kw):
        print('call %s():' % func.__name__)
        return func(*args, **kw)
    return wrapper
  • 偏函数
# 简单总结functools.partial的作用就是,把一个函数的某些参数给固定住(也就是设置默认值),返回一个新的函数,调用这个新函数会更简单
  • 模块

    每一个包目录下面都会有一个__init__.py的文件,这个文件是必须存在的,否则,Python 就把这个目录当成普通目录,而不是一个包。__init__.py可以是空文件,也可以有 Python 代码,因为__init__.py本身就是一个模块,而它的模块名就是mycompany

    • scope
    # 在一个模块中,我们可能会定义很多函数和变量,但有的函数和变量我们希望给别人使用,有的函数和变量我们希望仅仅在模块内部使用。在Python中,是通过_前缀来实现的
    
    # 类似__xxx__这样的变量是特殊变量
    

    在 Python 中,安装第三方模块,是通过包管理工具pip完成的,第三方库都会在 Python 官方的pypi.python.orgopen in new window网站注册

    默认情况下,Python 解释器会搜索当前目录、所有已安装的内置模块和第三方模块,搜索路径放在sys模块的path变量中

  • 类和实例 由于类可以起到模板的作用,因此,可以在创建实例的时候,把一些我们认为必须绑定的属性强制填写进去。通过定义一个特殊的__init__方法,在创建实例的时候,就把namescore等属性绑上去

    class Student(object):
    
        def __init__(self, name, score):
            self.name = name
            self.score = score
    # __init__方法的第一个参数永远是self,表示创建的实例本身
    
    # 如果要让内部属性不被外部访问,可以把属性的名称前加上两个下划线__,在Python中,实例的变量名如果以__开头,就变成了一个私有变量(private),只有内部可以访问,外部不能访问
    
    # 如果外部代码要获取name和score怎么办?可以给Student类增加get_name和get_score这样的方法,允许外部代码修改score怎么办?可以再给Student类增加set_score方法;你也许会问,原先那种直接通过bart.score = 99也可以修改啊,为什么要定义一个方法大费周折?因为在方法中,可以对参数做检查,避免传入无效的参数
    class Student(object):
        ...
    
        def set_score(self, score):
            if 0 <= score <= 100:
                self.__score = score
            else:
                raise ValueError('bad score')
    
    # 双下划线开头的实例变量是不是一定不能从外部访问呢?其实也不是。不能直接访问__name是因为Python解释器对外把__name变量改成了_Student__name,所以,仍然可以通过_Student__name来访问__name变量
    >>> bart._Student__name
    'Bart Simpson'
    # 但是强烈建议你不要这么干,因为不同版本的Python解释器可能会把__name改成不同的变量名
    
    • 动态语言的“鸭子类型”,它并不要求严格的继承体系,一个对象只要“看起来像鸭子,走起路来像鸭子”,那它就可以被看做是鸭子
    • 著名的“开闭”原则:对扩展开放:允许新增Animal子类;对修改封闭:不需要修改依赖Animal类型的run_twice()等函数
  • 如果要获得一个对象的所有属性和方法,可以使用dir()函数,它返回一个包含字符串的 list,比如,获得一个 str 对象的所有属性和方法

  • 类似__xxx__的属性和方法在 Python 中都是有特殊用途的,比如__len__方法返回长度。在 Python 中,如果你调用len()函数试图获取一个对象的长度,实际上,在len()函数内部,它自动去调用该对象的__len__()方法,所以,下面的代码是等价的

    >>> len('ABC')
    3
    >>> 'ABC'.__len__()
    3
    
    # 我们自己写的类,如果也想用len(myObj)的话,就自己写一个__len__()方法
    >>> class MyDog(object):
    ...     def __len__(self):
    ...         return 100
    ...
    >>> dog = MyDog()
    >>> len(dog)
    100
    
  • slots

# Python允许在定义class的时候,定义一个特殊的__slots__变量,来限制该class实例能添加的属性
class Student(object):
    __slots__ = ('name', 'age') # 用tuple定义允许绑定的属性名称
>>> s = Student() # 创建新的实例
>>> s.name = 'Michael' # 绑定属性'name'
>>> s.age = 25 # 绑定属性'age'
>>> s.score = 99 # 绑定属性'score'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Student' object has no attribute 'score'
  • @property
# Python内置的@property装饰器就是负责把一个方法变成属性调用的
class Student(object):

    @property
    def score(self):
        return self._score

    @score.setter
    def score(self, value):
        if not isinstance(value, int):
            raise ValueError('score must be an integer!')
        if value < 0 or value > 100:
            raise ValueError('score must between 0 ~ 100!')
        self._score = value

# 要特别注意:属性的方法名不要和实例变量重名,是因为调用s.birth时,首先转换为方法调用,在执行return self.birth时,又视为访问self的属性,于是又转换为方法调用,造成无限递归,最终导致栈溢出报错RecursionError
  • 定制类
# __str__()返回用户看到的字符串,而__repr__()返回程序开发者看到的字符串,也就是说,__repr__()是为调试服务的
class Student(object):
    def __init__(self, name):
        self.name = name
    def __str__(self):
        return 'Student object (name=%s)' % self.name
    __repr__ = __str__

>>> class Student(object):
...     def __init__(self, name):
...         self.name = name
...     def __str__(self):
...         return 'Student object (name: %s)' % self.name
...
>>> print(Student('Michael'))
Student object (name: Michael)
  • 枚举
from enum import Enum
Month = Enum('Month', ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'))

# 这样我们就获得了Month类型的枚举类,可以直接使用Month.Jan来引用一个常量,或者枚举它的所有成员, value属性则是自动赋给成员的int常量,默认从1开始计数

# 如果需要更精确地控制枚举类型,可以从Enum派生出自定义类
from enum import Enum, unique

@unique
class Weekday(Enum):
    Sun = 0 # Sun的value被设定为0
    Mon = 1
    Tue = 2
    Wed = 3
    Thu = 4
    Fri = 5
    Sat = 6
# @unique装饰器可以帮助我们检查保证没有重复值。
  • TODO 元类 metaclass,当我们定义了类以后,就可以根据这个类创建出实例,所以:先定义类,然后创建实例
# type()函数既可以返回一个对象的类型,又可以创建出新的类型,比如,我们可以通过type()函数创建出Hello类,而无需通过class Hello(object)...的定义
Hello = type('Hello', (object,), dict(hello=fn)) # 创建Hello class

IO

  • open()
try:
    f = open('/path/to/file', 'r')
    print(f.read())
finally:
    if f:
        f.close()try:
    f = open('/path/to/file', 'r')
    print(f.read())
finally:
    if f:
        f.close()

# Python引入了with语句来自动帮我们调用close()方法:
with open('/path/to/file', 'r') as f:
    print(f.read())
  • 序列化
# 我们把变量从内存中变成可存储或传输的过程称之为序列化, 序列化之后,就可以把序列化后的内容写入磁盘,或者通过网络传输到别的机器上, 把变量内容从序列化的对象重新读到内存里称之为反序列化

# Python提供了pickle模块来实现序列化

# JSON表示出来就是一个字符串,可以被所有语言读取,也可以方便地存储到磁盘或者通过网络传输。JSON不仅是标准格式,并且比XML更快,而且可以直接在Web页面中读取,非常方便
# JSON标准规定JSON编码是UTF-8,所以我们总是能正确地在Python的str与JSON的字符串之间转换

Data Type

Last Updated:
Contributors: Moui