iOS 获取设备型号(iPhone+iPad)*新总结

在游戏开发中,我们常常需要获取iOS设备信息来进行数据统计和性能适配。

比如在数据统计时,需要获取当前设备的名称,类型,系统名称,系统版本,UUID等,这些都可以通过UIDevice类来获取。代码如下:

NSString *deviceName = [[UIDevice currentDevice] name]; // 设备名称,如YIYI的iPhone
NSString *deviceModel = [[UIDevice currentDevice] model]; // 设备类型,如 iPad
NSString *osName = [[UIDevice currentDevice] systemName]; // 系统名称,如 iOS
NSString *osVersion = [[UIDevice currentDevice] systemVersion]; // 系统版本,如12.3
NSString *uuid = [[NSUUID UUID] UUIDString]; // UUID 是 Universally Unique Identifier 的缩写,中文意思是通用唯一识别码.

在性能适配时,我们往往要建立一张机型配置表,指定游戏在特定机型上的性能配置参数,从而使高中低机型的玩家都获得相对*好的游玩体验。这就需要获取设备的机型信息,但Apple并没有提供直接的API获取,不过由于iOS是基于UNIX改进的,所以我们可以通过UNIX中放置系统硬件配置信息的struct utsname类的machine属性来获取设备标签,然后从http://theiphonewiki.com/wiki/Models获取*新的设备标签,机型型号的对照表,再通过这种映射关系获取相应的设备型号。代码如下:

#import “sys/utsname.h”

+ (NSString *)deviceName {
struct utsname systemInfo;
uname(&systemInfo);
NSString *internalName = [NSString stringWithCString:systemInfo.machine encoding:NSUTF8StringEncoding];

NSDictionary *dict = [
// iPhone
@”iPhone1,1″ : @”iPhone”,
@”iPhone1,2″ : @”iPhone 3G”,
@”iPhone2,1″ : @”iPhone 3GS”,
@”iPhone3,1″ : @”iPhone 4″,
@”iPhone3,2″ : @”iPhone 4″,
@”iPhone3,3″ : @”iPhone 4″,
@”iPhone4,1″ : @”iPhone 4S”,
@”iPhone5,1″ : @”iPhone 5″,
@”iPhone5,2″ : @”iPhone 5″,
@”iPhone5,3″ : @”iPhone 5c”,
@”iPhone5,4″ : @”iPhone 5c”,
@”iPhone6,1″ : @”iPhone 5s”,
@”iPhone6,2″ : @”iPhone 5s”,
@”iPhone7,2″ : @”iPhone 6″,
@”iPhone7,1″ : @”iPhone 6 Plus”,
@”iPhone8,1″ : @”iPhone 6s”,
@”iPhone8,2″ : @”iPhone 6s Plus”,
@”iPhone8,4″ : @”iPhone SE (1st generation)”,
@”iPhone9,1″ : @”iPhone 7″,
@”iPhone9,3″ : @”iPhone 7″,
@”iPhone9,2″ : @”iPhone 7 Plus”,
@”iPhone9,4″ : @”iPhone 7 Plus”,
@”iPhone10,1″ : @”iPhone 8″,
@”iPhone10,4″ : @”iPhone 8″,
@”iPhone10,2″ : @”iPhone 8 Plus”,
@”iPhone10,5″ : @”iPhone 8 Plus”,
@”iPhone10,3″ : @”iPhone X”,
@”iPhone10,6″ : @”iPhone X”,
@”iPhone11,8″ : @”iPhone XR”,
@”iPhone11,2″ : @”iPhone XS”,
@”iPhone11,6″ : @”iPhone XS Max”,
@”iPhone11,4″ : @”iPhone XS Max”,
@”iPhone12,1″ : @”iPhone 11″,
@”iPhone12,3″ : @”iPhone 11 Pro”,
@”iPhone12,5″ : @”iPhone 11 Pro Max”,
@”iPhone12,8″ : @”iPhone SE (2nd generation)”,
// iPad
@”iPad1,1″ : @”iPad”,
@”iPad2,1″ : @”iPad 2″,
@”iPad2,2″ : @”iPad 2″,
@”iPad2,3″ : @”iPad 2″,
@”iPad2,4″ : @”iPad 2″,
@”iPad3,1″ : @”iPad (3rd generation)”,
@”iPad3,2″ : @”iPad (3rd generation)”,
@”iPad3,3″ : @”iPad (3rd generation)”,
@”iPad3,4″ : @”iPad (4th generation)”,
@”iPad3,5″ : @”iPad (4th generation)”,
@”iPad3,6″ : @”iPad (4th generation)”,
@”iPad6,11″ : @”iPad (5th generation)”,
@”iPad6,12″ : @”iPad (5th generation)”,
@”iPad7,5″ : @”iPad (6th generation)”,
@”iPad7,6″ : @”iPad (6th generation)”,
@”iPad7,11″ : @”iPad (7th generation)”,
@”iPad7,12″ : @”iPad (7th generation)”,
// iPad Air
@”iPad4,1″ : @”iPad Air”,
@”iPad4,2″ : @”iPad Air”,
@”iPad4,3″ : @”iPad Air”,
@”iPad5,3″ : @”iPad Air 2″,
@”iPad5,4″ : @”iPad Air 2″,
@”iPad11,3″ : @”iPad Air (3rd generation)”,
@”iPad11,4″ : @”iPad Air (3rd generation)”,
// iPad Pro
@”iPad6,7″ : @”iPad Pro (12.9-inch)”,
@”iPad6,8″ : @”iPad Pro (12.9-inch)”,
@”iPad6,3″ : @”iPad Pro (9.7-inch)”,
@”iPad6,4″ : @”iPad Pro (9.7-inch)”,
@”iPad7,1″ : @”iPad Pro (12.9-inch) (2nd generation)”,
@”iPad7,2″ : @”iPad Pro (12.9-inch) (2nd generation)”,
@”iPad7,3″ : @”iPad Pro (10.5-inch)”,
@”iPad7,4″ : @”iPad Pro (10.5-inch)”,
@”iPad8,1″ : @”iPad Pro (11-inch)”,
@”iPad8,2″ : @”iPad Pro (11-inch)”,
@”iPad8,3″ : @”iPad Pro (11-inch)”,
@”iPad8,4″ : @”iPad Pro (11-inch)”,
@”iPad8,5″ : @”iPad Pro (12.9-inch) (3rd generation)”,
@”iPad8,6″ : @”iPad Pro (12.9-inch) (3rd generation)”,
@”iPad8,7″ : @”iPad Pro (12.9-inch) (3rd generation)”,
@”iPad8,8″ : @”iPad Pro (12.9-inch) (3rd generation)”,
@”iPad8,9″ : @”iPad Pro (11-inch) (2nd generation)”,
@”iPad8,10″ : @”iPad Pro (11-inch) (2nd generation)”,
@”iPad8,11″ : @”iPad Pro (12.9-inch) (4th generation)”,
@”iPad8,12″ : @”iPad Pro (12.9-inch) (4th generation)”,
// iPad mini
@”iPad2,5″ : @”iPad mini”,
@”iPad2,6″ : @”iPad mini”,
@”iPad2,7″ : @”iPad mini”,
@”iPad4,4″ : @”iPad mini 2″,
@”iPad4,5″ : @”iPad mini 2″,
@”iPad4,6″ : @”iPad mini 2″,
@”iPad4,7″ : @”iPad mini 3″,
@”iPad4,8″ : @”iPad mini 3″,
@”iPad4,9″ : @”iPad mini 3″,
@”iPad5,1″ : @”iPad mini 4″,
@”iPad5,2″ : @”iPad mini 4″,
@”iPad11,1″ : @”iPad mini (5th generation)”,
@”iPad11,2″ : @”iPad mini (5th generation)”,
];

NSString *model = [dict objectForKey:internalName];
return model;
}

iOS运用fabric记录crash日志过程

访问官网地址(进行注册账号):

https://fabric.io

下载客户端地址:

https://fabric.io/downloads

1:注册成功后,并把客户端软件下载后,就可以登录客户端进行操作,选择要增加的工程文件

%title插图%num%title插图%num%title插图%num

2:运用客户端,生成脚本

%title插图%num%title插图%num

因为这边是直接采用把fabric框架直接拉进到项目中,所以生成的脚本为这种样式,若是采用Pod引入,其脚本会不一样;脚本的引入都会在项目的Info.Plist产生一个配置采单;

%title插图%num

3:把脚本复制到XCode项目的相关地方

%title插图%num%title插图%num%title插图%num

 注意:当有一个项目多个targets时,要对每个targets进行run Script设置,确保每个targets里面的info.plist文件有生成相应的配置,否则运行会报错;

4:引入相应的框架文件,直接从客户端拉到项目中

%title插图%num%title插图%num%title插图%num

注意:除了直接把fabric拉进项目引用,还可以用POD进行管理插件,只是其脚本的内容格式不一样;

5:在项目中引入文件,并初始化框架,注册并特意编写错误的代码

%title插图%num%title插图%num%title插图%num

 6:根据客户端提示运行*后一步,点Done回去,等待程序发布

%title插图%num%title插图%num

7:回到XCODE的项目中,对项目进行发布

%title插图%num%title插图%num

注意:选择Release,然后进行Archive;

8:当Archive成功发布以后,客户端会有提示,是否要进行dsym的上传

%title插图%num%title插图%num%title插图%num%title插图%num%title插图%num

注意:选择Distribute,进入下一个页面,此处可以输入接受通知的邮件地址,可以是多人接收,然后下一步提示语输入,然后开始进行上传dysm文件;

9:成功运行以后就可以查看错误的信息

%title插图%num%title插图%num

注意:其实fabric的原理还是把发布后的dsym上传后对它进行定位,显示出错误的位置;如果不用客户端这种上传,也可以中完成到脚本的加入后,把发布生成的dysm压缩成包进行上传;后官网对应的项目进行操作,如下图:

%title插图%num%title插图%num%title插图%num

合并两个排序的链表

合并两个排序的链表

题目链接:
https://leetcode-cn.com/problems/he-bing-liang-ge-pai-xu-de-lian-biao-lcof/

题意:
输入两个递增排序的链表,合并这两个链表并使新链表中的节点仍然是递增排序的。

题解:
链表中的归并排序,不断比较两个头指针指向节点的值大小,选择较小的加入链表。

代码:
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None

class Solution:
def mergeTwoLists(self, l1: ListNode, l2: ListNode) -> ListNode:
cur = dum = ListNode(0)
while l1 and l2:
if l1.val < l2.val:
cur.next, l1 = l1, l1.next
else:
cur.next, l2 = l2, l2.next
cur = cur.next
cur.next = l1 if l1 else l2
return dum.next

适用于国内的 NTP 服务器地址,可用于时间同步或 Android 加速 GPS 定位

NTP 是什么?
NTP 是网络时间协议(Network Time Protocol),它用来同步网络设备【如计算机、手机】的时间的协议。

NTP 实现什么目的?
目的很简单,就是为了提供准确时间。因为我们的手表、手机、电脑等设备,经常会跑着跑着时间就出现了误差,或快或慢的少几秒,时间长了甚至误差过分钟。

NTP 服务器列表
*常见、熟知的就是 www.pool.ntp.org/zone/cn,国内地址为:cn.pool.ntp.org

Windows 系统上自带的俩个:time.windows.com 和 time.nist.gov

Mac OS X 上自带的俩个:time.apple.com 和 time.asia.apple.com

一个国内无偿提供的 NTP 服务器,速度挺快,但地址池有两个 IP 已不可用,我已邮件给官方。官网:NTP授时快速域名服务,NTP 服务器:cn.ntp.org.cn

来自阿里云的 NTP 服务器:

ntp.aliyun.com

ntp1.aliyun.com

ntp2.aliyun.com

ntp3.aliyun.com

ntp4.aliyun.com

ntp5.aliyun.com

ntp6.aliyun.com

ntp7.aliyun.com

注意! ntp.aliyun.com 和 ntp2 – ntp7 均为同一个服务器,实际只有 ntp.aliyun.com 和 ntp1.aliyun.com 两个服务器。
1
新增一个国家授时中心:ntp.ntsc.ac.cn

Android 中怎么修改 NTP 服务器地址加速 GPS 定位呢?
要修改这货,除了部分第三方 ROM 在设置里直接可以修改外,就只能 ROOT 后修改了。ROOT 有风险,请考虑清楚后再操作。

确保手机已 ROOT 并已安装上权限管理软件(例如 SU 什么的……就不多提了)
挂载 /system 读写权限(ES/RE 文件管理器里可以设置挂载)
进入 /system/etc 目录,找到 gps.conf 文件,先复制一个备份一下命名为 gps.conf.bak
编辑 gps.conf 文件,找到:NTP_SERVER=north-america.pool.ntp.org【也许值不一样,找到 NTP_SERVER 就对了】
修改其为上方任一 NTP 服务器地址即可,推荐 cn.ntp.org.cn 或阿里云,修改后保存并关闭即可,注意重启手机生效。

ython 爬虫ip代理

ython 爬虫ip代理

新建proxy_list.txt文本,一行一个ip代理ip地址,必要情况下可以加上端口号

import requests

url = ‘https://lgch.xyz/’

headers = {
‘User-Agent’: “Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.103 Safari/537.36”
}

# 读取ip代理池
with open(‘proxy_list.txt’, ‘r’) as f:
while True:
for line in f.readlines():
proxy = line.strip(‘\n’) # 删除每一行*后面的换行符
print(proxy)

proxy_support = {
‘http’: ‘http://’ + proxy
}

response = requests.get(url, proxies=proxy_support)
print(response.text)

 

python–列表切片的理解

python–列表切片的理解

[:]
[:] 分号前面表示起始索引,后面表示终止索引
str[a:b]表示截取字符串的a开始的位置,b表示结束位置。
如果为[a:] 并且a为负数表示输出列表中*后a个元素。
如果为[:b] 并且b为负数则表示去除后几位。
如果起始索引为0则可以省略起始索引

# 索引从0开始。
players = [‘ming’, ‘hong’, ‘hei’, ‘lihua’]
# [0:3]中0表示从0开始进行输出,3表示在3的前一个元素输出完后停止
print(player[0:3])
# 输出结果: [‘ming’, ‘hong’, ‘hei’]
# [2:]表示起始索引指定为2,并省略终止索引
print(player[2:])
# 输出结果: [‘hong’, ‘hei’, ‘lihua’]

[::]
[::] *个分号前面表示起始索引,*个分号后面表示终止索引
*后一个分号后面表示步长为多少

n = 123456
str(n)[::-1]
# [::1]中省略起止位置,步进为-1
# 输出结果:654321

python中步进为正,从左往右取,步进为负,从右往左取

str(n)[::-1] 可以实现字符串翻转

nginx静态资源服务器简单配置

传统的web项目,一般都将静态资源存放在 webroot的目录下,这样做很方便获取静态资源,但是如果说web项目很大,用户很多,静态资源也很多时,服务器的性能 或许就会很低下了。这种情况下一般都会需要一个静态资源的服务器。

搭建nginx服务器首先得安装nginx服务,关于nginx服务的安装可以参考我的另一篇博客《nginx服务安装》这里直接介绍静态服务器的配置
进入nginx安装目录的conf目录下,修改nginx.conf文件,在一个server{}中添加 一个location 部分配置代码如下

root@ubuntu:/usr/local/nginx/conf# vi nginx.conf
server {
listen 80;
server_name localhost;
location / {
root html;
index index.html index.htm;
}
location /image/ {
root /usr/local/myImage/;
autoindex on;
}

}

从上面的配置可以看出来 端口为80,server_name为localhost(写ip地址也可以)

location /image/ {
root /usr/local/myImage/;
autoindex on;
}

这个配置表示输入 localhost:80/image/ 时会访问本机的/usr/local/myImage/image/ 目录。所以要新建/usr/local/myImage/image/ 目录,同时还要在nginx安装目录的html目录中新建一个 与 location中 image同名的image目录,虽然该目录里面什么也没有,在/usr/local/my Image/image/ 中我们放一张图片1.jpg上去,重启nginx服务,就可以通过 localhost:80/image/1.jpg访问了

root@ubuntu:/usr/local/nginx/html# mkdir image

root@ubuntu:/usr/local/nginx/html# mkdir /usr/local/myImage/image
#放一张照片上去#
root@ubuntu:/usr/local/nginx/html# cd /usr/local/myImage/image
root@ubuntu:/usr/local/myImage/image# ls
1.jpg
root@ubuntu:/usr/local/myImage/image#

重启 nginx

root@ubuntu:/usr/local/nginx/sbin# ./nginx -s reload
root@ubuntu:/usr/local/nginx/sbin#

打开浏览器 输入 server_name:80/image/1.jpg 就可以访问该静态图片了

您也可以移除掉root 和 autoindex 配置,直接在html目录下的image目录下新建一张图片1.jpg。

或者在nginx.conf 配置中在server下配置root 如下

server {
listen 88;
server_name localhost;
root /home/ubuntu/static/;
}

访问 http:hostName:88/dir/file 就会自动访问 服务器/home/ubuntu/static/dir 目录下的文件file

注意nginx的启动用户,确保该用户有权限访问目录下的文件
新版本nginx的启动用户为 www-data!

括号匹配

括号匹配

括号序列
链接: https://www.bilibili.com/video/BV1j54y1x7wj?from=search&seid=6645398165143026340.

题目描述
给出一个仅包含字符’(’,’)’,’{’,’}’,’[‘和’]’,的字符串,判断给出的字符串是否是合法的括号序列。
括号必须以正确的顺序关闭,”()“和”()[]{}“都是合法的括号序列,但”(]“和”([)]“不合法。
示例1:
输入:”[”
返回:“false”
示例2:
输入:”[]”
返回:“true”
解题思路
看到括号匹配的问题,首选的就是用栈解决。
对于本题中给出的三种括号,当我们遇到‘(’,‘{’,‘[’,就往栈里加入对应的反括号即‘)’,‘}’,‘]’,然后开始遍历序列,把对应的括号加入栈中,并进行匹配。
对于“[]}}”这中样例,在遍历的时候,中途过程中我们的栈就会变空,所以这种情况可以直接返回false;对于“[}”这种样例,就是括号不匹配,因为我们遇见括号的时候会和栈顶元素进行比较,而这种样例,肯定和栈顶元素匹配不成功的,所以返回false;*后还需要判一下栈是否为空,因为会存在这样的样例:“[[[[”。

遍历括号序列,遇到左括号的情况就入栈,遇到右括号就将当前栈中栈顶元素出栈,当前栈中栈顶元素与当前遍历的右括号进行比较,看是否匹配,从而验证其合法性。

整个括号的序列遍历完成后,如果栈为空则表示该括号序列为合法的,如果此时栈中仍有元素,即栈不为空,则说明该括号序列不合法,返回false
如果遍历中遇到右括号时,栈为空或者没有与当前右括号匹配的左括号,则说明该括号序列为不合法的,返回false
要考虑括号序列中只有一个元素的情况,直接返回false
完整代码
# @param s string字符串
# @return bool布尔型
#
class Solution:
def isValid(self , s ):
# write code here
stack = []
for i in s:
if len(stack)==0:
stack.append(i)
continue
if i in [‘(‘,'[‘,'{‘]:
stack.append(i)
if i == ‘)’ and stack[-1] == ‘(‘:
stack.pop()
if i == ‘[‘ and stack[-1] == ‘]’
stack.pop()
if i == ‘{‘ and stack[-1] == ‘}’
stack.pop()
else:
return false
# 循环结束后判断栈是否为空,栈为空说明匹成功,栈不为空说明匹配失败
if not stack:
return true
else:
return false

Web自动化测试

Web自动化测试

Menu:selenium的安装 8page
Selenium的简介
支持web浏览器的自动化。主要由三个工具构成:WebDriver、IDE、Grid

Selenium环境配置步骤
1.准备好python环境
2.准备好selenium环境
3.下载浏览器对应的driver版本
4.driver配置环境变量
5.在python中import对应的依赖

Selenium的安装
前提:
配置好python环境
配置好pip工具
安装:
pip install selenium
或者在pycharm直接安装

Driver的配置
Driver的介绍
https://www.selenium.dev/documentation/en/webdriver/driver_requirements/
Driver的下载
淘宝镜像:https://npm.taobao.org/mirrors/chromedriver/
官方网站:https://chromedriver.storage.googleapis.com/index.html
Driver的安装:
找到和自己浏览器版本适配的driver版本
导入到环境变量中
Mac
1.下载好driver包进行解压,命令行通过命令unzip chromedriver_mac64.zip
2.ls查看解压的文件
3.命令行通过命令./chromedriver 执行 查看driver的版本、端口号等信息
4.vim ~/.bash_profile ,export PATH=$PATH:/Users/lixu/software #/Users/lixu/software代表的是driver存放的目录
5.source ~/.bash_profile #使配置生效
Windows
1.下载好driver包进行解压
2.在环境变量中配置
3.重启命令行以及pycharm
4.命令行通过命令chromedriver 查看是否配置成功

#python中如何使用
import selenium

from selenium import webdriver
def test_selenium():
driver = webdriver.Chrome()
driver.get(“https://www.baidu.com”)

SeleniumIDE用例录制 9page
下载、安装
Chrome插件:
https://chrome.google.com/webstore/detail/selenium-ide/mooikfkahbdckldjjndioackbalphokd
Firefox插件:
https://addons.mozilla.org/en-US/firefox/addon/selenium-ide
启动IDE
安装完成后,通过在浏览器的菜单栏中点击图标来启动

IDE的使用

SeleniumIDE用例编写 7page
官方文档 https://selenium-python-zh.readthedocs.io/en/latest/

用例的关键要素
导入依赖
创建driver
执行自动化步骤
断言

Selenium等待 Menu:6page
直接等待
隐式等待
显式等待

直接等待
强制等待,线程休眠一定时间 time.sleep(3)

隐式等待
设置一个等待时间,轮询查找(默认0.5秒)元素是否出现,如果没出现就抛出异常
全局等待
self.driver.implicitly_wait(3)

显式等待
在代码中定义等待条件,当条件发生时才继续执行代码
‘WebDriverWait’配合until()和until_not()方法,根据判断条件进行等待
程序每隔一段时间(默认为0.5秒)进行条件判断,如果条件成立则执行下一步,否则继续等待,直到超过设置的*长时间

#显式等待
#使用自带的内置函数
condition = expected_conditions.element_to_be_clickable((By.XPATH,”//*[@class=’ember-view’]”))
WebDriverWait(self.driver,10).until(condition)

#显式等待
#使用自定义的方法
def wait(x): #该方法必须要传个参数,就算编写的代码不需要使用,也需要填写参数,供until方法使用
return len(self.driver.find_elements(By.XPATH,”//*[@class=’ember-view’]”)) >= 1 #通过查找’*新’的class属性长度来判断元素是否找到,这里使用的是find_elements
WebDriverWait(self.driver,10).until(wait) #until需要传入一个方法 此时传入的wait是没带参数的,如果是wait()表示的是调用,而不是传入方法了,请一定注意!

import time

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions
from selenium.webdriver.support.wait import WebDriverWait

class TestWait:
def setup(self):
self.driver = webdriver.Chrome()
self.driver.get(‘http://home.testing-studio.com’)
#隐式等待
self.driver.implicitly_wait(3)

def teardown(self):
pass

#直接等待
def test_wait(self):
self.driver.find_element_by_link_text(“所有分类”).click()

#期望按钮‘*新’是可点击的状态时才允许运行点击热门按钮的操作

# def wait(x): #该方法必须要传个参数,就算编写的代码不需要使用,也需要填写参数,供until方法使用
# return len(self.driver.find_elements(By.XPATH,”//*[@class=’ember-view’]”)) >= 1
# WebDriverWait(self.driver,10).until(wait) #until需要传入一个方法 此时传入的wait是没带参数的,如果是wait()表示的是调用,而不是传入方法了

#使用自带的内置函数
condition = expected_conditions.element_to_be_clickable((By.XPATH,”//*[@class=’ember-view’]”))
WebDriverWait(self.driver,10).until(condition)
self.driver.find_element_by_link_text(“热门”).click()
time.sleep(2)
print(“hello”)

web控件定位与常见操作

 

python的gin库的介绍和使用

python的gin库的介绍和使用

python的gin的介绍和使用
本文参考官方文档

1.简介
由于现在很多机器学习的实验需要设置繁琐的参数,在多次实验中,有些参数是一样的,为了方便设置参数,Gin库出现了。它允许函数或类被注释为@gin.configurable,这使得能够使用清晰而强大的语法通过简单的配置文件来设置它们的参数。这种方法减少了配置维护,同时使实验配置透明且易于重复。

简单理解,gin像一个封装了参数配置的类,使用这个类将使得大量的参数配置变得简单清晰

安装

pip install gin-config
1
2.@gin.configurable
任何函数和类都可以使用@gin.configurable装饰器

@gin.configurable
def my_network(images, num_outputs, num_layers=3, weight_decay=1e-4):

@gin.configurable装饰器做了如下三件事:

把类或函数声明成了可配置的东西
它决定了函数或类构造函数的哪些参数是可配置的(默认情况下是其所有的参数)
封装类或函数,拦截调用,并向函数的可配置参数提供来自参数设置全局注册表的值(这些值是类或函数声明时没有指定的值)
为了确定哪些是可以配置的参数,@gin.configurable会使用到allowlist和denylist参数,分别声明哪些是可配的哪些是不可配的,我们通常用一个即可,默认没有用allowlist指定的都为不可配,反之亦然。

@gin.configurable(‘supernet’, denylist=[‘images’])
def my_network(images, num_outputs, num_layers=3, weight_decay=1e-4):

其中supernet是我们指定的配置名。

3.赋值
我们使用如下两种格式给参数赋值:

gin.bind_parameter(‘configurable_name.parameter_name’, value)
configurable_name.parameter_name = value
具体例子分别如下:

gin.bind_parameter(‘supernet.num_layers’, 5)
gin.bind_parameter(‘supernet.weight_decay’, 1e-3)

supernet.num_layers = 5
supernet.weight_decay = 1e-3

4.取值
我们可以用gin.query_parameter来取值,具体例子如下

num_layers = gin.query_parameter(‘supernet.num_layers’)
weight_decay = gin.query_parameter(‘supernet.weight_decay’)

5.配置参考文件
假如我们有以下代码:

@gin.configurable
class DNN(object):
def __init__(self, num_units=(1024, 1024)):

def __call__(inputs, num_outputs):

@gin.configurable(denylist=[‘data’])
def train_model(network_fn, data, learning_rate, optimizer):

我们可以在gin文件里配置参数:

train_model.network_fn = @DNN() # An instance of DNN is passed.
train_model.optimizer = @MomentumOptimizer # The class itself is passed.
train_model.learning_rate = 0.001

DNN.num_units = (2048, 2048, 2048)
MomentumOptimizer.momentum = 0.9

上面显示了两种配置参数风格。@DNN()和@MomentumOptimizer。对于前者将会调用DNN类的实例参数,且每次参数配置都会随着每个DNN类的实例变动。对于后者将会调用类MomentumOptimizer的默认参数。

6.使用gin文件
我们经常会和absl下flags一起使用gin,比如下面这样

from absl import flags

flags.DEFINE_multi_string(
‘gin_file’, None, ‘List of paths to the config files.’)
flags.DEFINE_multi_string(
‘gin_param’, None, ‘Newline separated list of Gin parameter bindings.’)

FLAGS = flags.FLAGS

然后主程序main.py里*先解析参数:

gin.parse_config_files_and_bindings(FLAGS.gin_file, FLAGS.gin_param)

假设我们参数文件example.gin在当前目录下,则运行时,我们在终端输入python main.py –gin_file=example.gin

也可以在代码里改成这样:

flags.DEFINE_multi_string(
‘gin_file’, [“example.gin”], ‘List of paths to the config files.’)

然后直接运行

6.调用其他类或函数
我们可以用下面代码调用其他类或函数的参数,甚至这个类或函数可以在其他项目里。

gin.external_configurable(tf.train.MomentumOptimizer)

7.范围限定
当一个可配置函数在程序执行过程中被多次调用时,可能需要为每次调用提供不同的参数绑定。Gin提供了一个范围限定机制来促进这一点。
例如,假设我们想要实现一个GAN,我们必须交替训练一个生成器和一个鉴别器。在Tensoflow中,这*容易通过两个优化器来实现,因此我们可能有这样一个函数:

gin.external_configurable(tf.train.GradientDescentOptimizer)

@gin.configurable(allowlist=[‘generator_optimizer’, ‘discriminator_optimizer’])
def gan_trainer(
generator_loss, generator_vars, generator_optimizer,
discriminator_loss, discriminator_vars, discriminator_optimizer):
# Construct the optimizers and minimize w.r.t. the correct variables.
generator_train_op = generator_optimizer().minimize(
generator_loss, generator_vars)
discriminator_train_op = discriminator_optimizer().minimize(
discriminator_loss, discriminator_vars)

我们如何将generator_optimizer和discriminator_optimizer都配置为@GradientDescentOptimizer,但具有不同的学习速率?
下面是个错误示范:

# Won’t work!
gan_trainer.generator_optimizer = @GradientDescentOptimizer
GradientDescentOptimizer.learning_rate = 0.01

gan_trainer.discriminator_optimizer = @GradientDescentOptimizer
# This binding will overwrite the previous one:
GradientDescentOptimizer.learning_rate = 0.001

Gin提供了一个范围界定机制来处理这种情况。任何可配置引用的前面都可以有一个作用域名称,用/字符与可配置名称分开。同样,也可以通过在可配置名称前面加上一个范围名称来应用特定于某个范围的绑定。
下面是对的示范:

# This will work! Use scoping to apply different parameter bindings.
gan_trainer.generator_optimizer = @generator/GradientDescentOptimizer
gan_trainer.discriminator_optimizer = @discriminator/GradientDescentOptimizer

generator/GradientDescentOptimizer.learning_rate = 0.01
discriminator/GradientDescentOptimizer.learning_rate = 0.001

8.标记gin参数
Gin允许您指示在Gin配置中必须提供某些参数。这可以通过两种方式实现:
1.在函数的调用位置
2.在函数的签名中

当调用一个可配置时,您可以通过gin.REQUIRED标记任何arg或kwarg。所需对象:

my_network(images, gin.REQUIRED, num_layers=5, weight_decay=gin.REQUIRED)

将在调用时检查所需参数。如果没有为这些参数提供Gin绑定,将会引发一个错误,列出缺少的参数绑定以及需要它们的可配置名称。
定义可配置时,可以使用gin.REQUIRED将参数标记为必需的:

@gin.configurable
def run_training(model_dir=gin.REQUIRED, network=gin.REQUIRED, …):

9.从Gin文件中导入模块
import some.module.spec

10.在Gin文件中调用另一个Gin文件参数
一个Gin文件可以包含其他Gin文件,这样可以更容易地将一个配置拆分成单独的组件(例如,一个“基础”配置,它被其他派生配置包含和修改)。包含另一个Gin文件可以使用以下语法完成:

include ‘path/to/another/file.gin’
1
11.Gin “macros”
有时一个值应该在多个绑定之间共享。为了实现这一点并避免多次重复该值(导致维护负担),Gin提供了以下预定义的可配置功能:

@gin.configurable
def macro(value):
return value

可以引用“宏”函数(通过“()”来取值)。例如:

num_layers/macro.value = 10
network.num_layers = @num_layers/macro()

也可以这样写

num_layers = 10
network.num_layers = %num_layers

12.常量
gin.constant函数可用于定义常量,这些常量可通过上述宏语法访问。例如,在Python中:

gin.constant(‘THE_ANSWER’, 42)

然后在配置文件gin中

meaning.of_life = %THE_ANSWER

请注意,任何Python对象都可以用作常量的值(包括不能表示为Gin文字的对象)。值将被存储到Gin内部字典中,直到程序终止,因此避免创建具有有限生命周期的值的常数。
一个消除歧义的模块可以放在常量名称的前面。例如:

gin.constant(‘some.modules.PI’, 3.14159)

13.实验使用多个Gin文件和额外的命令行绑定
在许多情况下,可以定义多个包含实验整体配置不同部分的Gin文件。对整体配置的额外“调整”可以通过命令行标志作为单独的绑定来传递。

一种推荐的方法是创建一个包含多个Gin配置的文件夹,然后创建一个包含以下内容的BUILD文件:

filegroup(
name = “gin_files”,
srcs = glob([“*.gin”]),
visibility = [“:internal”],
)

此filegroup可用作二进制文件中的数据依赖项:

data = [“//path/to/configs:gin_files”,]
1
在二进制文件中,可以定义以下标志:

from absl import flags

flags.DEFINE_multi_string(
‘gin_file’, None, ‘List of paths to the config files.’)
flags.DEFINE_multi_string(
‘gin_param’, None, ‘Newline separated list of Gin parameter bindings.’)

FLAGS = flags.FLAGS

然后用Gin解析它们:

gin.parse_config_files_and_bindings(FLAGS.gin_file, FLAGS.gin_param)
1
*后,二进制文件可以运行为:

…/run_gin_eval \
–gin_file=$CONFIGS_PATH/cartpole_balance.gin \
–gin_file=$CONFIGS_PATH/base_dqn.gin \
–gin_file=$CONFIGS_PATH/eval.gin \
–gin_param=’evaluate.num_episodes_eval = 10′ \
–gin_param=’evaluate.generate_videos = False’ \
–gin_param=’evaluate.eval_interval_secs = 60′