Python

views 3366 words

getattr, getitem….

    __getattribute__只适用于所有的`.`运算符
    __getitem__只适用于所有的`[]`运算符
    __getattr__解决了访问不存在的属性报错问题, 找不到属性时会被调用
    __get__作为数据描述符,那么此对象只能作为类属性,作为实例属性则无效

collections

from collections import defaultdict, UserDict

UserDict:

from collections import UserDict 

d = {'a':1, 
    'b': 2, 
    'c': 3} 
  
# Creating an UserDict 
userD = UserDict(d) 
print(userD.data) 
>> {'a': 1, 'b': 2, 'c': 3}
  
# Creating an empty UserDict 
userD = UserDict() 
print(userD.data) 
>> {}

# Creating a Dictionary where deletion is not allowed 
class MyDict(UserDict): 
      
    # Function to stop deleltion from dictionary 
    def __del__(self): 
        raise RuntimeError("Deletion not allowed") 
          
    # Function to stop pop from dictionary 
    def pop(self, s = None): 
        raise RuntimeError("Deletion not allowed") 
          
    # Function to stop popitem from Dictionary 
    def popitem(self, s = None): 
        raise RuntimeError("Deletion not allowed") 
      
# Driver's code 
d = MyDict({'a':1, 
    'b': 2, 
    'c': 3}) 
  
print("Original Dictionary") 
print(d) 
>> "Original Dictionary"
>> {'a': 1, 'c': 3, 'b': 2}
  
d.pop(1)
>> RuntimeError: Deletion not allowed

Example2 - 一个增强的字典类, 允许对字典使用 “加/+” :

from collections import UserDict

class FancyDict(UserDict):

    def __init__(self, data = {}, **kw):
        UserDict.__init__(self)
        self.update(data)
        self.update(kw)

    def __add__(self, other):
        tmp_dict = FancyDict(self.data)
        tmp_dict.update(b)
        return tmp_dict

a = FancyDict(a = 1)
b = FancyDict(b = 2)

print(a + b)
>> {'a': 1, 'b': 2}

class/static/abstract method

https://medium.com/citycoddee/python%E9%80%B2%E9%9A%8E%E6%8A%80%E5%B7%A7-2-static-class-abstract-methods%E4%B9%8B%E5%AF%A6%E7%8F%BE-1e3b3998bccf

property

封裝是個很基本的要素.不讓外部使用者直接碰到物件的內容,而只能透過方法來修改或取用內部的屬性.

class gundam(object):
    def __init__(self, driver):
        self.driver = driver

g1 = gundam('Charon')
g1.driver
>> 'Charon'

g1.driver = 'Alan'
g1.driver
>> 'Alan'

雖然可以实例化高达class (初始化時要填入駕駛名字),和取得駕駛名字,但是這樣的方式也會讓使用者輕易把駕駛替換掉 為了安全,將駕駛換成私有屬性

class gundam(object):
    def __init__(self, driver):
        self._driver = driver

这是如果再用上面的方法就会报错

Get 和 Set

可以換個寫法,加上 get 和 set 方法來操作私有屬性

class gundam(object):
    def __init__(self, driver):
        self._driver = driver
    def get_driver(self):
        return self._driver
    def set_driver(self, new_driver):
        self._driver = new_driver

g2 = gundam('Charon')
g2.get_driver()
>> 'Charon'

g2.set_driver('Alan')
g2.get_driver()
>> 'Alan'

Property

如果不想讓取出那麼麻煩, Python 有一種方式可以將私有屬性讓使用者用起來像公開屬性一樣,就是加上 property 這個裝飾器.

class gundam(object):
    def __init__(self, driver):
        self._driver = driver
    @property
    def driver(self):
        return self._driver
    def set_driver(self, new_driver):
        self._driver = new_driver

g3 = gundam('Charon')
g3.driver
>> 'Charon'

g3.driver = 'Alan'
>> AttributeError: can't set attribute

Getter 和 Setter

class gundam(object):
    def __init__(self, driver=None):
        self._driver = driver
    @property
    def driver(self):
        print('!!!get driver!!!')
        return self._driver
    @driver.setter
    def driver(self, new_driver):
        print('!!!set driver!!!')
        if type(new_driver) == str:
            self._driver = new_driver
        else:
            raise TypeError('driver need str.')

g4 = gundam('Charon')
>> '!!!set driver!!!'
g4.driver
>> '!!!get driver!!!'
>> 'Charon'

g4.driver = 'Alan'
>> '!!!set driver!!!'
g4.driver
>> '!!!get driver!!!'
>> 'Alan'

一開始初始化的時候,雖然是寫 self.driver = driver ,這時候就會呼叫 setter 函數,使用者輸入的參數便指派到 self._driver 中.之後用 g4.driver 的時候就會直接取出 self._driver 出來

用起來就跟一開始的範例一模一樣,但是背後其實是在操作私有屬性.使用者也更容易透過 setter 方法來管理使用者輸入的值

Pandas

pandas.DataFrame.resample 根据时间聚合采样

https://blog.csdn.net/brucewong0516/article/details/84768464

DataFrame.resample(rule, how=None, axis=0, fill_method=None, closed=None,
				    label=None, convention='start', kind=None, loffset=None,
				    limit=None, base=0, on=None, level=None)

pandas.DataFrame.pct_change 计算Percentage change

https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.pct_change.html

pandas.DataFrame.to_dict dataframe转换成dict

orient: str {‘dict’, ‘list’, ‘series’, ‘split’, ‘records’, ‘index’}
Determines the type of the values of the dictionary.

‘dict’ (default) : dict like {column -> {index -> value}}
‘list’ : dict like {column -> [values]}
‘series’ : dict like {column -> Series(values)}
‘split’ : dict like {‘index’ -> [index], ‘columns’ -> [columns], ‘data’ -> [values]}
‘records’ : list like [{column -> value}, … , {column -> value}]
‘index’ : dict like {index -> {column -> value}}

Convert dict into a dataframe

def dict_to_df(d):
    df=pd.DataFrame(d.items(), columns=['Date', 'DateValue'])
    df.set_index(0, inplace=True)
    # df['Date'] = pd.to_datetime(df['Date'])
    return df

change a single index/column value in pandas dataframe

df.rename(index={'2020-07-30':'2020-07-25'},inplace=True)
df.rename(column={'Adj Close':'amount'},inplace=True)

convert Pandas Timestamp index to list of date strings

df.index.strftime("%Y-%m-%d")

Display all dataframe columns in a Jupyter Python Notebook

import pandas as pd
pd.set_option('display.max_columns', <number of columns>)
# can do the same for the rows too:
pd.set_option('display.max_rows', <number of rows>)

get DataFrame.pct_change to calculate monthly change on daily price data

### resample the data to business mont
In [31]: from pandas.io import data as web

# read some example data, note that this is not exactly your data!
In [32]: s = web.get_data_yahoo('AAPL', start='2009-01-02',
...                             end='2009-12-31')['Adj Close']

# resample to business month and return the last value in the period
In [34]: monthly = s.resample('BM').apply(lambda x: x[-1])

In [35]: monthly
Out[35]: 
Date
2009-01-30     89.34
2009-02-27     88.52
2009-03-31    104.19
...
2009-10-30    186.84
2009-11-30    198.15
2009-12-31    208.88

In [36]: monthly.pct_change()
Out[36]: 
Date
2009-01-30         NaN
2009-02-27   -0.009178
2009-03-31    0.177022
...
2009-10-30    0.016982
2009-11-30    0.060533
2009-12-31    0.054151

Delete column from pandas DataFrame

# The best way to do this in pandas is to use drop:

df = df.drop('column_name', 1)
# where 1 is the axis number (0 for rows and 1 for columns.)

# To delete the column without having to reassign df you can do:

df.drop('column_name', axis=1, inplace=True)
# Finally, to drop by column number instead of by column label, try this to delete, e.g. the 1st, 2nd and 4th columns:

df = df.drop(df.columns[[0, 1, 3]], axis=1)  # df.columns is zero-based pd.Index 
# Also working with "text" syntax for the columns:

df.drop(['column_nameA', 'column_nameB'], axis=1, inplace=True)

count the NaN values in a column in pandas DataFrame

s = pd.Series([1,2,3, np.nan, np.nan])

s.isna().sum()   # or s.isnull().sum() for older pandas versions
# 2

__init__.py

init.py该文件的作用就是相当于把自身整个文件夹当作一个包来管理,每当有外部import的时候,就会自动执行里面的函数

例子1:

.
└── test
    ├── A
    │   ├── A_A
    │   │   ├── A_A_A.py
    │   │   └── A_A_B.py
    │   └── A_B.py
    └── B
        └── run.py

建立了一个test文件夹,其中一个文件夹A打算建立成一个软件包,然后尝试在B文件夹的的run.py文件下导入A包中的模块

在B/run.py中运行以下语句的结果为:

Sentence Result
import A ImportError: No module named A
import A.A_A ImportError: No module named A.A_A
import A.A_B ImportError: No module named A.A_B

结论:此时A并不能被当成是一个软件包

例子2:

.
└── test
    ├── A
    │   ├── A_A
    │   │   ├── A_A_A.py
    │   │   └── A_A_B.py
    │   ├── __init__.py
    │   └── A_B.py
    └── B
        └── run.py

A文件夹下包含init.py

在B/run.py中运行以下语句的结果为:

Sentence Result
import A 成功
import A.A_A ImportError: No module named A.A_A
import A.A_B 成功

结论:由A.A_B能被成功import看出此时A已经是一个软件包,因为A下的.py文件已经能够程序识别出来. 但是由于A下的A_A还不是一个软件包,所以A.A_A还不能被导入

例子3:

.
└── test
    ├── A
    │   ├── A_A
    │   │   ├── __init__.py
    │   │   ├── A_A_A.py
    │   │   └── A_A_B.py
    │   ├── __init__.py
    │   └── A_B.py
    └── B
        └── run.py

A文件夹及其子文件夹下都包含init.py

在B/run.py中运行以下语句的结果为:

Sentence Result
import A 成功
import A.A_A 成功
import A.A_B 成功

结论:此时A及其子文件夹A_A都成功变成软件包,其中的模块可以被任意导入

Python Thread

使用 futures 处理并发: https://www.jianshu.com/p/62145aed2d49

无线程:

# No thread
import time
start_time = time.time()
ppl = 500
def action(num):
    global ppl
    while ppl > 0:
        ppl -= 50
        print(f'车辆编号: {num}, 当前车站人数: {ppl}')
        time.sleep(1)

num = 1
action(num)
print(f'Duration: {time.time()-start_time}')
'''
车辆编号: 1, 当前车站人数: 450
车辆编号: 1, 当前车站人数: 400
车辆编号: 1, 当前车站人数: 350
车辆编号: 1, 当前车站人数: 300
车辆编号: 1, 当前车站人数: 250
车辆编号: 1, 当前车站人数: 200
车辆编号: 1, 当前车站人数: 150
车辆编号: 1, 当前车站人数: 100
车辆编号: 1, 当前车站人数: 50
车辆编号: 1, 当前车站人数: 0
Duration: 10.034696340560913
'''

单线程:

# Signle thread
import time
# import threading
from threading import Thread
start_time = time.time()
ppl = 500
def action(num):
    global ppl
    while ppl > 0:
        ppl -= 50
        print(f'车辆编号: {num}, 当前车站人数: {ppl}')
        time.sleep(1)

num = 1
vehicle = Thread(target=action, args=(num,))
vehicle.start()
vehicle.join()
print(f'Duration: {time.time()-start_time}')
'''
车辆编号: 1, 当前车站人数: 450
车辆编号: 1, 当前车站人数: 400
车辆编号: 1, 当前车站人数: 350
车辆编号: 1, 当前车站人数: 300
车辆编号: 1, 当前车站人数: 250
车辆编号: 1, 当前车站人数: 200
车辆编号: 1, 当前车站人数: 150
车辆编号: 1, 当前车站人数: 100
车辆编号: 1, 当前车站人数: 50
车辆编号: 1, 当前车站人数: 0
Duration: 10.028159141540527
'''

多线程 (传递对象方式):

# Multi thread 1
import time
# import threading
from threading import Thread
start_time = time.time()
ppl = 500
def action(num):
    global ppl
    while ppl > 0:
        ppl -= 50
        print(f'车辆编号: {num}, 当前车站人数: {ppl}')
        time.sleep(1)

vehicles = []  # 新建车辆组
for num in range(1,6):
    vehicle = Thread(target=action, args=(num,))  # 新建车辆
    vehicles.append(vehicle)   # 添加车辆到车辆组中
for vehicle in vehicles:
    vehicle.start()          # 分别启动车辆
for vehicle in vehicles:
    vehicle.join()          # 分别检查到站车辆
print(f'Duration: {time.time()-start_time}')
'''
车辆编号: 1, 当前车站人数: 450
车辆编号: 2, 当前车站人数: 400
车辆编号: 3, 当前车站人数: 350
车辆编号: 4, 当前车站人数: 300
车辆编号: 5, 当前车站人数: 250
车辆编号: 2, 当前车站人数: 200
车辆编号: 1, 当前车站人数: 150
车辆编号: 3, 当前车站人数: 100
车辆编号: 4, 当前车站人数: 50
车辆编号: 5, 当前车站人数: 0
Duration: 2.0072731971740723
'''

多线程 (覆盖子类方式):

# Multi thread 2
import time
# import threading
from threading import Thread
start_time = time.time()
ppl = 500

class MyThread(Thread):
    def __init__(self, num):
        super(MyThread, self).__init__()
        self.num = num

    def run(self):
        global ppl
        while ppl > 0:
            ppl -= 50
            print(f'车辆编号: {self.num}, 当前车站人数: {ppl}')
            time.sleep(1)

vehicles = []       # 新建车辆组
for num in range(1,6):        # 设置车辆数
    vehicle = MyThread(num)     # 新建车辆
    vehicles.append(vehicle)     # 添加车辆到车辆组中
    vehicle.start()     #启动车辆
for vehicle in vehicles:
    vehicle.join()      # 分别检查到站车辆
print(f'Duration: {time.time()-start_time}')
'''
车辆编号: 1, 当前车站人数: 450
车辆编号: 2, 当前车站人数: 400
车辆编号: 3, 当前车站人数: 350
车辆编号: 4, 当前车站人数: 300
车辆编号: 5, 当前车站人数: 250
车辆编号: 1, 当前车站人数: 200
车辆编号: 2, 当前车站人数: 150
车辆编号: 3, 当前车站人数: 100
车辆编号: 4, 当前车站人数: 50
车辆编号: 5, 当前车站人数: 0
Duration: 2.00836181640625
'''

小结:

  1. 不使用线程类和使用单线程运行时间是一样的,因为正常执行一个脚本,本质上就是单线程
  2. 创建多线程的两种方法运行时间也是一样的,因为最终都是交给Thread类来处理,自行选择即可
  3. 多线程运行时间明显比单线程快的多,从理论上来说是和线程数成正比的,但是实际并非是线程越多越好,因为线程越多所消耗的资源也就越多

Thread类提供了以下方法:

  • run(): 用以表示线程活动的方法
  • start():启动线程活动
  • join([time]): 等待至线程中止. 这阻塞调用线程直至线程的join() 方法被调用中止
    • 正常退出或者抛出未处理的异常
    • 或者是可选的超时发生
  • isAlive(): 返回线程是否活动的
  • getName(): 返回线程名
  • setName(): 设置线程名

说明:

  1. 创建线程对象后,必须通过调用线程的start() 方法启动其活动,这将在单独的控制线程中调用run()方法
  2. 一旦线程的活动开始,线程就被认为是“活着的”,当run()方法终止时,它会停止活动,或者引发异常
  3. 线程可以调用is_alive()方法测试是否处于活动状态,其他线程可以调用线程的join()方法,这将阻塞调用线程,直到调用其join()方法的线程终止
  4. 线程有一个名称,这个名称可以传递给构造函数,并通过name属性读取或更改
  5. 线程可以标记为“守护程序线程”,这个标志的意义在于,当只剩下守护进程线程时,整个Python程序都会退出,可以通过守护程序属性设置该标志

Python ZMQ

  • ZMQ (以下 ZeroMQ 简称 ZMQ)是一个简单好用的传输层,像框架一样的一个 socket library,他使得 Socket 编程更加简单、简洁和性能更高
  • 是一个消息处理队列库,可在多个线程、内核和主机盒之间弹性伸缩. ZMQ 的明确目标是“成为标准网络协议栈的一部分,之后进入 Linux 内核”
  • ZMQ 让编写高性能网络应用程序极为简单和有趣
  • ZeroMQ并不是一个对socket的封装,不能用它去实现已有的网络协议
  • 它有自己的模式,不同于更底层的点对点通讯模式
  • 它有比tcp协议更高一级的协议。(当然ZeroMQ不一定基于TCP协议,它也可以用于进程间和进程内通讯)
  • zeromq 并不是类似rabbitmq消息列队,它实际上只一个消息列队组件,一个库

Request-Reply模式(请求响应模型)

  • 客户端在请求后,服务端必须回响应
  • 由客户端发起请求,并等待服务端响应请求
    • 从客户端端来看,一定是一对对发收配对的
    • 反之,在服务端一定是收发对
    • 服务端和客户端都可以是1:N的模型. 通常把1认为是server,N认为是Client.
  • ZMQ可以很好的支持路由功能(实现路由功能的组件叫做Device),把1:N扩展为N:M(只需要加入若干路由节点)
  • 从这个模型看,更底层的端点地址是对上层隐藏的. 每个请求都隐含回应地址,而应用则不关心它

server.py

import zmq
import sys
context = zmq.Context()
socket = context.socket(zmq.REP)
socket.bind("tcp://*:5555")
while True:
    try:
        print("wait for client ...")
        message = socket.recv()
        print("message from client:", message.decode('utf-8'))
        socket.send(message)
    except Exception as e:
        print('异常:',e)
        sys.exit()

client.py

import zmq
import sys
context = zmq.Context()
print("Connecting to server...")
socket = context.socket(zmq.REQ)
socket.connect("tcp://localhost:5555")
while True:

    input1 = input("请输入内容:").strip()
    if input1 == 'b':
        sys.exit()
    socket.send(input1.encode('utf-8'))

    message = socket.recv()
    print("Received reply: ", message.decode('utf-8'))

Publish-Subscribe模式(发布订阅模型)

广播所有client,没有队列缓存,断开连接数据将永远丢失. client可以进行数据过滤.

server.py

import zmq
import time
import sys
context = zmq.Context()
socket = context.socket(zmq.PUB)
socket.bind("tcp://*:5555")

while True:
    msg = input("请输入要发布的信息:").strip()
    if msg == 'b':
        sys.exit()
    socket.send(msg.encode('utf-8'))
    time.sleep(1)

client1.py

import zmq

context = zmq.Context()
socket = context.socket(zmq.SUB)
socket.connect("tcp://localhost:5555")
socket.setsockopt(zmq.SUBSCRIBE,''.encode('utf-8'))  # 接收所有消息
while True:
    response = socket.recv().decode('utf-8');
    print("response: %s" % response)

client2.py

import zmq

context = zmq.Context()
socket = context.socket(zmq.SUB)
socket.connect("tcp://localhost:5555")
socket.setsockopt(zmq.SUBSCRIBE,'123'.encode('utf-8'))  # 消息过滤  只接受123开头的信息
while True:
    response = socket.recv().decode('utf-8');
    print("response: %s" % response)

结果:

### server发布以下信息(注意:b是关闭发布端的指令):
请输入要发布的信息:hello python
请输入要发布的信息:发布以下信息
请输入要发布的信息:123435678
请输入要发布的信息:123三部分
请输入要发布的信息:广播模式,发布端只关心发布信息,不关心订阅端是否接收
请输入要发布的信息:b

### client1接收的信息:
response: hello python
response: 发布以下信息
response: 123435678
response: 123三部分
response: 广播模式,发布端只关心发布信息,不关心订阅端是否接收

### client2接收的信息 (只接收123开头的信息):
response: 123435678
response: 123三部分

Parallel Pipeline模式(管道模型)

由三部分组成:

  • push进行数据推送,
  • work进行数据缓存,
  • pull进行数据竞争获取处理.

区别于Publish-Subscribe存在一个数据缓存和处理负载

当连接被断开,数据不会丢失,重连后数据继续发送到对端.

server.py

import zmq
import time

context = zmq.Context()
socket = context.socket(zmq.PUSH)
socket.bind("tcp://*:5557")

while True:
    msg = input("请输入要发布的信息:").strip()
    socket.send(msg.encode('utf-8'))
    print("已发送")
    time.sleep(1)

worker.py

import zmq
context = zmq.Context()
receive = context.socket(zmq.PULL)
receive.connect('tcp://127.0.0.1:5557')
sender = context.socket(zmq.PUSH)
sender.connect('tcp://127.0.0.1:5558')

while True:
    data = receive.recv()
    print("正在转发...")
    sender.send(data)

client.py

import zmq
context = zmq.Context()
socket = context.socket(zmq.PULL)
socket.bind("tcp://*:5558")

while True:
    response = socket.recv().decode('utf-8')
    print("response: %s" % response)

结果:

### server端:
请输入要发布的信息:hello  python
已发送
请输入要发布的信息:不可阻挡
已发送
请输入要发布的信息:123abc
已发送
请输入要发布的信息:

### work端:
正在转发...
正在转发...
正在转发...

### client端:(接收第二条信息后断开,断开后重新收到的信息)
response: 123abc

Python 打包成Mac app

在Mac下打包用Tkinter开发出来的应用程序

安装

pip3 install py2app

创建setup.py文件

  1. 进入项目工程 cd 项目路径
  2. 创建setup.py py2applet --macke-setup python文件

    • E.g. py2applet --macke-setup main.py
    • 可能报错:py2applet:command not found
    • 解决:
      • sudo find / -name "py2applet" -type f
    • 成功了会显示Wrote setup.py,在你的工程下会有setup.py文件
    • 若是setup.py文件创建失败, 报错, 报错信息为:

      *** creating application bundle: MyApp ***
      error: [Errno 1] Operation not permitted
      MacOSX El Captain引入的SIP(系统完整性保护)功能会影响py2app创建应用
      • 禁用SIP csrutil status
        • csrutil disable:SIP安全已关闭
        • csrutil enable:SIP安全已开启
      • 设置SIP
        • 重启Mac,同时按住Command+R,直到进入Recovery Model
        • 点击Utilities —>Terminal
        • 在Terminal输入csrutil disablecsrutil enabel,回车
        • 重启Mac,完成
      • 删除受限制的文件标志
        • sudo chflags -R norestricted /System/Library/Frameworks/Python.framework
  3. 设置程序图标

    • Easyicon网站下载了icns格式的图标,将图标另存为到桌面上存放脚本的文件夹
    • 打开setup.py文件,修改其中OPTIONS内容

      OPTIONS = {
      'iconfile':'icon.icns'
      }
    • 也可以使用命令的参数模式直接生成带有图标设置的setup文件:

      • py2applet --make-setup main.py icon.icns
  4. 打包应用程序

    • 清除旧的内容 rm -rf build dist
    • 创建app应用 python3 setup.py py2app
      • python setup.py py2app -A
      • 给其他没有sdk的电脑使用, 包括lib库.(没有安装sdk的电脑使用, 需要去掉-A,将把所有的依赖全部打包)
    • 等待创建,创建完成后,项目目录会多build和dist文件夹,程序应用就在dist文件下

Python Seed

import numpy as np
num=0
while(num<5):
    np.random.seed(1)
    print(np.random.random())
    num+=1
 
print('-------------------------')
 
num1=0
np.random.seed(2)
while(num1<5):
    print(np.random.random())
    num1+=1

运行结果:

0.417022004702574
0.417022004702574
0.417022004702574
0.417022004702574
0.417022004702574
---------------------
0.43599490214200376
0.025926231827891333
0.5496624778787091
0.4353223926182769
0.42036780208748903

众所周知,所谓随机数其实是伪随机数,所谓的‘伪’,意思是这些数其实是有规律的,只不过因为算法规律太复杂,很难看出来.

所谓巧妇难为无米之炊,再厉害的算法,没有一个初始值,它也不可能凭空造出一系列随机数来,诶,种子就是这个初始值.

可以将随机数生成的算法看成一个黑盒,把准备好的种子扔进去,它会返给你两个东西,一个是随机数,另一个是保证能生成下一个随机数的新的种子,把新的种子放进黑盒,又得到一个新的随机数和一个新的种子….

第一段代码把对种子的设置放在了循环里面,每次执行循环都旗帜鲜明地告诉黑盒:“我的种子是1”. 那么很显然:同一个黑盒,同一个种子,自然得到的是同一个随机数.

第二段代码把对种子的设置放在了循环外面,它只在第一次循环的时候明确地告诉黑盒:“我的种子是2”. 那么也很显然:从第二次循环开始,黑盒用的就是自己生成的新种子了.

Python eval & ast.literal_eval

作用: 把数据还原成它本身或者是能够转化成的数据类型

使用eval可以实现从元祖,列表,字典型的字符串到元祖,列表,字典的转换. 此外,eval还可以对字符串型的输入直接计算. 比如,她会将’1+1’的计算串直接计算出结果:

str_list = '[1,2,3,4]'
true_list = eval(str_list)
print(true_list)  # [1,2,3,4]
print(type(true_list),type(str_list))  # <class 'list'> <class 'str'>

str_tuple = '(1,2,3,4)'
true_tuple = eval(str_tuple)
print(true_tuple)  # (1,2,3,4)
print(type(true_tuple),type(str_tuple))  # <class 'tuple'> <class 'str'>

str_dict = "{'name':'Charon',}"
true_dict = eval(str_dict)
print(true_dict)  # [1,2,3,4]
print(type(true_dict),type(str_dict))  # <class 'dict'> <class 'str'>

value = eval(input('Plz enter a math formula: ')) # Plz enter a math formula: 1+1
print(value) # 2

eval强大的背后,是巨大的安全隐患. 比如说,用户恶意输入下面的字符串:

open(r'/Desktop/filename.txt', 'r').read()
__import__('os').system('dir')
__import__('os').system('rm -rf /etc/*')

那么eval就会显示你电脑目录结构,读取文件,删除文件…..如果是格盘等更严重的操作也一样执行.

所以这里就引出了另外一个安全处理方式ast.literal_eval. 官网解释:

Safely evaluate an expression node or a Unicode or Latin-1 encoded string containing a Python expression. The string or node provided may only consist of the following Python literal structures: strings, numbers, tuples, lists, dicts, booleans, and None.

This can be used for safely evaluating strings containing Python expressions from untrusted sources without the need to parse the values oneself.

ast模块就是帮助Python应用来处理抽象的语法解析的. 而该模块下的literal_eval()函数:则会判断需要计算的内容计算后是不是合法的python类型,如果是则进行运算,否则就不进行运算.

比如说上面的计算操作,及危险操作,如果换成了ast.literal_eval(), 都会拒绝执行. 报值错误,不合法的字符串.

import ast
value = ast.literal_eval('1+1')
print(value)
'''
Traceback (most recent call last):
  File "/Users/charon/Desktop/adviser-master/adviser/component_test.py", line 77, in <module>
    value = ast.literal_eval('1+1')
  File "/usr/local/Cellar/python/3.7.7/Frameworks/Python.framework/Versions/3.7/lib/python3.7/ast.py", line 91, in literal_eval
    return _convert(node_or_string)
  File "/usr/local/Cellar/python/3.7.7/Frameworks/Python.framework/Versions/3.7/lib/python3.7/ast.py", line 90, in _convert
    return _convert_signed_num(node)
  File "/usr/local/Cellar/python/3.7.7/Frameworks/Python.framework/Versions/3.7/lib/python3.7/ast.py", line 63, in _convert_signed_num
    return _convert_num(node)
  File "/usr/local/Cellar/python/3.7.7/Frameworks/Python.framework/Versions/3.7/lib/python3.7/ast.py", line 55, in _convert_num
    raise ValueError('malformed node or string: ' + repr(node))
ValueError: malformed node or string: <_ast.BinOp object at 0x12970a050>
'''

而只会执行合法的Python类型,从而大大降低系统的危险性. 所以出于安全考虑,对字符串进行类型转换的时候,最好使用ast.literal_eval()函数.

Python heapq

该模块提供了堆排序算法的实现。堆是二叉树,最大堆中父节点大于或等于两个子节点,最小堆父节点小于或等于两个子节点

针对问题: 找到该序列中最大或者最小的N个元素

一般方法

### 普通列表
l = [1,2,4,3,6,0,5,7,9]
sorted(l)
print("前三个:{},后三个:{}".format(l[:3], l[-3:]))
# 前三个:[1, 2, 4],后三个:[5, 7, 9]

### 复杂列表
scoreInfo=[

{'name':'Lucy','en_score': 89.6,'math_scroe':94.1},

{'name':'Bob','en_score': 72.4,'math_scroe':84.2},

{'name':'LiLei','en_score': 82.6,'math_scroe':74.1},

{'name':'HanMeimei','en_score': 65.6,'math_scroe':86.9},

{'name':'Lily','en_score': 78.1,'math_scroe':65.8},

{'name':'Tracy','en_score': 72.6,'math_scroe':65.4},

]
# 按照英语成绩'en_score'对学生进行排名,找出前3名和后3名
t_scoreInfo = sorted([item for item in scoreInfo], key=lambda x: x['en_score'], reverse=True)

print('前三个:{}\n后三个:{}'.format(t_scoreInfo[:3], t_scoreInfo[-3:]))

# 前三个:[{'name': 'Lucy', 'en_score': 89.6, 'math_scroe': 94.1}, {'name': 'LiLei', 'en_score': 82.6, 'math_scroe': 74.1}, {'name': 'Lily', 'en_score': 78.1, 'math_scroe': 65.8}]

# 后三个:[{'name': 'Tracy', 'en_score': 72.6, 'math_scroe': 65.4}, {'name': 'Bob', 'en_score': 72.4, 'math_scroe': 84.2}, {'name': 'HanMeimei', 'en_score': 65.6, 'math_scroe': 86.9}]

使用内置函数heapq

import heapq

# 简单序列

l = [1,2,4,3,6,0,5,7,9]
print('前三:{}'.format(heapq.nlargest(3, l))) # 前三:[9, 7, 6]
print('后三:{}'.format(heapq.nsmallest(3, l))) # 后三:[0, 1, 2]

# 复杂序列
high =heapq.nlargest(3, scoreInfo, key=lambda s: s['en_score'])

low = heapq.nsmallest(3, scoreInfo, key=lambda s: s['en_score'])

print("前三:{}\n后三:{}".format(high, low))

# 前三:[{'name': 'Lucy', 'en_score': 89.6, 'math_scroe': 94.1}, {'name': 'LiLei', 'en_score': 82.6, 'math_scroe': 74.1}, {'name': 'Lily', 'en_score': 78.1, 'math_scroe': 65.8}]
# 后三:[{'name': 'HanMeimei', 'en_score': 65.6, 'math_scroe': 86.9}, {'name': 'Bob', 'en_score': 72.4, 'math_scroe': 84.2}, {'name': 'Tracy', 'en_score': 72.6, 'math_scroe': 65.4}]

nlargest()和nsmallest()函数的底层实现是:先将序列数据进行堆排序后放入一个列表中

堆(heap)的数据结构有以下优点:

  1. heap[0]永远是最小的元素
  2. 其余元素可通过调用 heapq.heappop()方法得到,该方法会先将第一个元素弹出来,然后用下一个最小的元素来取代被弹出元素(这种操作时间复杂度仅仅是O(log N),N是堆大小)

通过上面的描述,如果查找最小的三个元素,其实等价于分别从堆结构中使用heappop()方法弹出3个值即可

heapq的其他方法:

heapq有两种方式创建堆, 一种是使用一个空列表,然后使用heapq.heappush()函数把值加入堆中,另外一种就是使用heap.heapify(list)转换列表成为堆结构

### 方法一:使用heappush()方法
heap = []
data = [2,3,5,7,9,23,14,16,12,10]

for i in data:
    heapq.heappush(heap,i)

print(heap) # [2, 3, 5, 7, 9, 23, 14, 16, 12, 10] !!没有进行排序!!

### 方法二:使用heapify()方法
data = [2,3,5,7,9,23,14,16,12,10]
heapq.heapify(data)
# 此时,直接变换data数据为堆结构,并没有返回新的数据


### 排序
##方法一:使用heapq.nlargest()和heapq.nsmallest()方法实现
heapq.nXXXest(num, set, key)

- num:表示返回数据的个数
- set:表示要处理的序列(当然集合最好,没有重复元素)
- key:表示排序规则

##方法二: 使用heappop()可以弹出heap中的数据。此时,弹出的数据就是排序后的数据
lst = []
while heap:
    lst.append(heapq.heappop(heap))

print(lst) # [2, 3, 5, 7, 9, 10, 12, 14, 16, 23]

对heapq使用排序场合给出几点建议:

  • 查找(排序)元素个数相对序列较小时,函数 nlargest()和 nsmallest()是最佳选择
  • 查找序列唯一的最小、最大的元素,推荐使用min()和max()函数最快
  • 查找(排序)元素个数和序列个数接近时,先排序、再切片,这样会更快

* 和 ** 运算符

算数运算

# *  代表乘法
# ** 代表乘方
>>> 2 * 5
10
>>> 2 ** 5
32

函数形参

*args 和 **kwargs 主要用于函数定义

可以将不定数量的参数传递给一个函数. 不定的意思是:预先并不知道, 函数使用者会传递多少个参数给你, 所以在这个场景下使用这两个关键字. 其实并不是必须写成 *args 和 *kwargs.  (星号) 才是必须的. 你也可以写成 *ar  和 k. 而写成 *args 和kwargs 只是一个通俗的命名约定.

python函数传递参数的方式有两种:

  • 位置参数(positional argument)
  • 关键词参数(keyword argument)

*args 与 **kwargs 的区别, 两者都是 python 中的可变参数:

  • *args 表示任何多个无名参数, 它本质是一个 tuple
  • **kwargs 表示关键字参数, 它本质上是一个 dict

如果同时使用 *args 和 **kwargs 时, 必须 *args 参数列要在 **kwargs 之前

def fun(*args, **kwargs):
    print('args=', args)
    print('kwargs=', kwargs)

fun(1, 2, 3, 4, A='a', B='b', C='c', D='d')
# args= (1, 2, 3, 4)
# kwargs= {'A': 'a', 'B': 'b', 'C': 'c', 'D': 'd'}

使用 *args

def fun(name, *args):
    print('你好:', name)
    for i in args:
        print("你的宠物有:", i)

fun("Geek", "dog", "cat")
# 你好: Geek
# 你的宠物有: dog
# 你的宠物有: cat

使用 **kwargs

def fun(**kwargs):
    for key, value in kwargs.items():
        print("{0} 喜欢 {1}".format(key, value))

fun(Geek="cat", cat="box")
# Geek 喜欢 cat
# cat 喜欢 box

函数实参

如果函数的形参是定长参数, 也可以使用 *args 和 **kwargs 调用函数, 类似对元组和字典进行解引用:

def fun(data1, data2, data3):
    print("data1: ", data1)
    print("data2: ", data2)
    print("data3: ", data3)

args = ("one", 2, 3)
fun(*args)
# data1:  one
# data2:  2
# data3:  3
kwargs = {"data3": "one", "data2": 2, "data1": 3}
fun(**kwargs)
# data1:  3
# data2:  2
# data3:  one

序列解包

序列解包没有 **

a, b, *c = 0, 1, 2, 3
print(a)
# 0
print(b)
# 1
print(c)
# [2,3]

匿名函数

Python Anonymous/Lambda Function

double = lambda x: x * 2

print(double(5))  # 10

# same as below
def double(x):
   return x * 2

Lambda functions are used along with built-in functions like filter(), map() etc

With filter()

my_list = [1, 5, 4, 6, 8, 11, 3, 12]

new_list = list(filter(lambda x: (x%2 == 0) , my_list))

print(new_list) # [4, 6, 8, 12]

With map()

my_list = [1, 5, 4, 6, 8, 11, 3, 12]

new_list = list(map(lambda x: x * 2 , my_list))

print(new_list) # [2, 10, 8, 12, 16, 22, 6, 24]

闭包

當一個variable被使用時,會遵循 LEGB 的規則,也就是 Local、Enclosing、Global 與 Builtins

在一个内部函数中,对外部作用域的变量进行引用,(并且一般外部函数的返回值为内部函数),那么内部函数就被认为是闭包.

def startAt(x):
    def incrementBy(y):
         return x*y
    return incrementBy

注意python是可以返回一个函数的,这也是python的特性之一

维基百科中的解释:

在计算机科学中,闭包(英语:Closure),又称词法闭包(Lexical Closure)或函数闭包(function closures),是引用了自由变量的函数。这个被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外

# print_msg是外围函数
def print_msg():
    msg = "I'm closure"

    # printer是嵌套函数
    def printer():
        print(msg)

    return printer


# 这里获得的就是一个闭包
closure = print_msg()
# 输出 I'm closure
closure()
  • msg是一个局部变量,在print_msg函数执行之后应该就不会存在了. 但是嵌套函数引用了这个变量,将这个局部变量封闭在了嵌套函数中,这样就形成了一个闭包.
  • 闭包就是引用了自有变量的函数,这个函数保存了执行的上下文,可以脱离原本的作用域独立存在 (闭包可以保存当前的运行环境 (保存函数的状态信息,使函数的局部变量信息依然可以保存下来))

装饰器

普通装饰器:

import functools

def log(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        print('call %s():' % func.__name__)
        print('args = {}'.format(*args))
        return func(*args, **kwargs)

    return wrapper

这是一个打印出方法名及其参数的装饰器

调用:

@log
def test(p):
    print(test.__name__ + " param: " + p)
    
test("I'm a param")

### 输出:
call test():
args = I'm a param
test param: I'm a param

装饰器在使用时,用了@语法, 与下面的调用方式没有区别其实:

def test(p):
    print(test.__name__ + " param: " + p)

wrapper = log(test)
wrapper("I'm a param")

@语法只是将函数传入装饰器函数

带参数的装饰器

装饰器允许传入参数,一个携带了参数的装饰器将有三层函数,如下所示:

import functools

def log_with_param(text):
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            print('call %s():' % func.__name__)
            print('args = {}'.format(*args))
            print('log_param = {}'.format(text))
            return func(*args, **kwargs)

        return wrapper

    return decorator
    
@log_with_param("param")
def test_with_param(p):
    print(test_with_param.__name__)

内层的decorator函数的参数func是怎么传进去的?

将其@语法去除,恢复函数调用的形式:

# 传入装饰器的参数,并接收返回的decorator函数
decorator = log_with_param("param")
# 传入test_with_param函数
wrapper = decorator(test_with_param)
# 调用装饰器函数
wrapper("I'm a param")

输出结果与正常使用装饰器相同:

call test_with_param():
args = I'm a param
log_param = param
test_with_param

装饰器这一语法体现了Python中函数是第一公民,函数是对象、是变量,可以作为参数、可以是返回值,非常的灵活与强大.

deque (双向队列)

创建双向队列

import collections
d = collections.deque()

append(往右边添加一个元素)

import collections
d = collections.deque()
d.append(1)
d.append(2)
print(d)

#输出:deque([1, 2])

appendleft(往左边添加一个元素)

import collections
d = collections.deque()
d.append(1)
d.appendleft(2)
print(d)

#输出:deque([2, 1])

clear(清空队列)

import collections
d = collections.deque()
d.append(1)
d.clear()
print(d)

#输出:deque([])

copy(浅拷贝)

import collections
d = collections.deque()
d.append(1)
new_d = d.copy()
print(new_d)

#输出:deque([1])

count(返回指定元素的出现次数)

import collections
d = collections.deque()
d.append(1)
d.append(1)
print(d.count(1))

#输出:2

extend(从队列右边扩展一个列表的元素)

import collections
d = collections.deque()
d.append(1)
d.extend([3,4,5])
print(d)

#输出:deque([1, 3, 4, 5])

extendleft(从队列左边扩展一个列表的元素)

import collections
d = collections.deque()
d.append(1)
d.extendleft([3,4,5])
print(d)
#
# #输出:deque([5, 4, 3, 1])

index(查找某个元素的索引位置)

import collections
d = collections.deque()
d.extend(['a','b','c','d','e'])
print(d)
print(d.index('e'))
print(d.index('c',0,3))  #指定查找区间

#输出:deque(['a', 'b', 'c', 'd', 'e'])
#     4
#     2

insert(在指定位置插入元素)

import collections
d = collections.deque()
d.extend(['a','b','c','d','e'])
d.insert(2,'z')
print(d)

#输出:deque(['a', 'b', 'z', 'c', 'd', 'e'])

pop(获取最右边一个元素,并在队列中删除)

import collections
d = collections.deque()
d.extend(['a','b','c','d','e'])
x = d.pop()
print(x,d)

#输出:e deque(['a', 'b', 'c', 'd'])

popleft(获取最左边一个元素,并在队列中删除)

import collections
d = collections.deque()
d.extend(['a','b','c','d','e'])
x = d.popleft()
print(x,d)

#输出:a deque(['b', 'c', 'd', 'e'])

remove(删除指定元素)

import collections
d = collections.deque()
d.extend(['a','b','c','d','e'])
d.remove('c')
print(d)

#输出:deque(['a', 'b', 'd', 'e'])

reverse(队列反转)

import collections
d = collections.deque()
d.extend(['a','b','c','d','e'])
d.reverse()
print(d)

#输出:deque(['e', 'd', 'c', 'b', 'a'])

rotate(把右边元素放到左边)

import collections
d = collections.deque()
d.extend(['a','b','c','d','e'])
d.rotate(2)   #指定次数,默认1次
print(d)

#输出:deque(['d', 'e', 'a', 'b', 'c'])

sort和sorted

  • sorted() 函数对所有可迭代的对象进行排序操作, 产生一个新的列表
  • sort() 函数在原位重新排列列表, 如果指定参数, 则使用比较函数指定的比较函数

    >>> list1=[(8, 'Logan', 20), (2, 'Mike', 22), (5, 'Lucy', 19)]
    >>> list1.sort(key=lambda x:x[2])
    >>> list1
    [(5, 'Lucy', 19), (8, 'Logan', 20), (2, 'Mike', 22)]
    
    >>> a=[1,2,5,3,9,4,6,8,7,0,12]
    >>> a.sort(reverse=True)
    >>> a
    [12, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
  • 内部实现机制为:Timesort

  • 最坏时间复杂度为:O(n log n)

  • 空间复杂度为:O(n)

Timsort

结合了合并排序(merge sort)和插入排序(insertion sort)而得出的排序算法. 该算法找到数据中已经排好序的块/分区,每一个分区叫一个run,然后按规则合并这些run.

https://www.cnblogs.com/clement-jiao/p/9243066.html

why left+(right-left)/2 will not overflow?

在二分法中常见left+(right-left)//2, 所以就想问为什么不直接(right+left)//2??

因为后一种的最大值是 right+left 可能它过大而导致overflow, 但是前一种的最大值就是right, 保证确定不会overflow

You have left < right by definition.

As a consequence, right - left > 0, and furthermore left + (right - left) = right (follows from basic algebra).

And consequently left + (right - left) / 2 <= right. So no overflow can happen since every step of the operation is bounded by the value of right.

Python 位运算符

按位运算符是把数字看作二进制来进行计算的

a = 0011 1100

b = 0000 1101 -w746

a>>=2等价于a=a>>2(右移2位)

a = 60            # 60 = 0011 1100 
b = 13            # 13 = 0000 1101 
c = 0
 
c = a & b;        # 12 = 0000 1100
 
c = a | b;        # 61 = 0011 1101 
 
c = a ^ b;        # 49 = 0011 0001
 
c = ~a;           # -61 = 1100 0011
 
c = a << 2;       # 240 = 1111 0000
 
c = a >> 2;       # 15 = 0000 1111

Python copy & deepcopy

import copy

if __name__ == '__main__':
    a = [1, 2, 3, 4]
    b = copy.copy(a)
    c = copy.deepcopy(a)  
    e = a
    print(a == b)  # True 说明 a 和 b 所指向的对象的内容相同
    print(a is b)  # False 说明 a 和 b 所指向的不是同一个对象(地址不同)
    print(a == c)  # True 说明 a 和 c 所指向的对象的内容相同
    print(a is c)  # False 说明 a 和 c 所指向的不是同一个对象(地址不同)
    print(a == e)  # True 说明 a 和 e 所指向的对象的内容相同
    print(a is e)  # True 说明 a 和 e 所指向的是同一个对象(地址相同)

对于简单对象(普通元素如数字,字符串..)来说, 深复制和浅复制的执行结果是一样的,并没有什么差别.

复杂对象可以理解为另外包含其他简单对象的对象,也就是包含子对象的对象,例如:List中嵌套List,或者Dict中嵌套List等, 其拷贝:

import copy

if __name__ == '__main__':
    a = {'name': 'test', 'age': 56, 'address': [1, 2, 3, 4, 5]}
    b = copy.copy(a)
    print(a is b)  # False 说明 a 和 b 不是同一个对象的引用
    print(a['address'] is b['address'])  # True 说明 a中的address 和 b 中的 address 是同一个对象
    c = copy.deepcopy(a)
    print(a is c)  # False 说明 a 和 c 不是同一个对象的引用
    print(a['address'] is c['address'])  # False 说明 a中的address 和 c 中的 address 不是同一个对象

Python 内置函数

all()

all() 函数用于判断给定的可迭代参数 iterable 中的所有元素是否都为 TRUE,如果是返回 True,否则返回 False。

all(iterable)

# example
>>> all(['a', 'b', 'c', 'd'])  # 列表list,元素都不为空或0
True
>>> all(['a', 'b', '', 'd'])   # 列表list,存在一个为空的元素
False
>>> all([0, 12, 3])          # 列表list,存在一个为0的元素
False

>>> all([])             # 空列表
True
>>> all(())             # 空元组
True