日常工作中会遇到数据处理需求频繁变更的情况,有时候需要处理大量的Json任务。如果家纺提出一点改动,Python处理数据如何避免大量修改代码。
这个问题,对接数据的同学会经常碰到。我们本文来探讨下如何有效率完成这些工作。
一、名字变更需求
首先这个问题,现在实际生产环境会用到 一些 接口协议:比如说thrift,proto、avro等成熟接口协议。特别是跨部分对接的工作很少直接裸用Json。因为这些成熟的接口协议在数据传输大小、数据存储、序列化反序列化效率上以及跨多种语言支持上有很棒的表现。裸用JSON也不能说没有,因为JSON在人类代码可读性这方面还是有优势。如果不会考虑很重的性能,从基于简单些来说,有时候也会直接用JSON。
那么如果碰到接口字段变更,这里我们分为两种情况考虑减少字段变更带来的代码维护成本:
- JSON的字段变更
- Protobuf等字段变更
JSON的字段变更这里我在细化为
- 名字规范变更
- 业务需求变更
二、名字规范变更
在业务上线初期,团队可能不会考虑很多代码规范,赶时间上线。但是到了中后期,随着业务代码量膨胀,那么规范代码风格是减少维护成本的重要一环。
我们来看一个python的官方规范,俗称PEP8,其中我们直接跳到名字规范这一节
https://peps.python.org/pep-0008/#descriptive-naming-styles
其中列举了这些不规范的写法
b
(single lowercase letter)B
(single uppercase letter)lowercase
lower_case_with_underscores
UPPERCASE
UPPER_CASE_WITH_UNDERSCORES
CapitalizedWords
(or CapWords, or CamelCase – so named because of the bumpy look of its letters [4]). This is also sometimes known as StudlyCaps.Note: When using acronyms in CapWords, capitalize all the letters of the acronym. Thus HTTPServerError is better than HttpServerError.mixedCase
(differs from CapitalizedWords by initial lowercase character!)Capitalized_Words_With_Underscores
(ugly!)
那么如果碰到这样我们怎么进行有效率改造呢
2.1 pyhumps包
这里我介绍官方提供的一个pip包pyhumps。pyhumps提供了驼峰camel,去驼峰decamelize,pascal。kebab写法。比如说jack in the box我们用四种写法分别是:
- 驼峰:jackInTheBox
- 去驼峰:jack_in_the_box
- pascal: JackInTheBox
- kebab: jack-in-the-box
pyhumps的官方链接https://pypi.org/project/pyhumps/
安装包的方法:
代码语言:bash复制pipenv install pyhumps
使用场景:
- 转换名字
import humps
humps.camelize("jack_in_the_box") # jackInTheBox
humps.decamelize("rubyTuesdays") # ruby_tuesdays
humps.pascalize("red_robin") # RedRobin
humps.kebabize("white_castle") # white-castle
- Json转换词典keys
import humps
array = [{"attrOne": "foo"}, {"attrOne": "bar"}]
humps.decamelize(array) # [{"attr_one": "foo"}, {"attr_one": "bar"}]
array = [{"attr_one": "foo"}, {"attr_one": "bar"}]
humps.camelize(array) # [{"attrOne": "foo"}, {"attrOne": "bar"}]
array = [{'attr_one': 'foo'}, {'attr_one': 'bar'}]
humps.kebabize(array) # [{'attr-one': 'foo'}, {'attr-one': 'bar'}]
array = [{"attr_one": "foo"}, {"attr_one": "bar"}]
humps.pascalize(array) # [{"AttrOne": "foo"}, {"AttrOne": "bar"}]
- 检查名字是否遵守规范
import humps
humps.is_camelcase("illWearYourGranddadsClothes") # True
humps.is_pascalcase("ILookIncredible") # True
humps.is_snakecase("im_in_this_big_ass_coat") # True
humps.is_kebabcase('from-that-thrift-shop') # True
humps.is_camelcase("down_the_road") # False
humps.is_snakecase("imGonnaPopSomeTags") # False
humps.is_kebabcase('only_got_twenty_dollars_in_my_pocket') # False
# what about abbrevations, acronyms, and initialisms? No problem!
humps.decamelize("APIResponse") # api_response
三、业务需求变更:
如果裸用Json的情况下,业务要求变更名字。一个减少维护成本最简单的想法就是少hard code名字。名字用全局变量宏替代。比如说GLOBAL_NAME='name1', 业务代码都用GLOBAL_NAME。像这种名字可以放在constants包里面。然后代码哪里需要用到就去import Mymodule.Constants.
实际代码情况会是更复杂的情况。这源自于代码长期迭代,里面风格会变了又变,风格混用,名字各种版本的问题。特别是JSON key这种数据交换媒介,往往是自由风格的字符串。比如说拥有了混合风格的JSON
代码语言:json复制{"keyOne": "value1", "key_one": "value2"}
这里所有会更推从用更成熟的数据交换协议,比如说protobuf
比如说proto协议定义如下
代码语言:txt复制syntax = "proto2";
package tutorial;
message Person {
optional string name = 1;
optional int32 id = 2;
optional string email = 3;
enum PhoneType {
PHONE_TYPE_UNSPECIFIED = 0;
PHONE_TYPE_MOBILE = 1;
PHONE_TYPE_HOME = 2;
PHONE_TYPE_WORK = 3;
}
message PhoneNumber {
optional string number = 1;
optional PhoneType type = 2 [default = PHONE_TYPE_HOME];
}
repeated PhoneNumber phones = 4;
}
message AddressBook {
repeated Person people = 1;
}
与生成 Java 和 C protocol buffer 代码不同,Python protocol buffer 编译器不会直接生成数据访问代码。你将会看到addressbook_pb2.py
它为所有消息、枚举和字段以及一些神秘的空类生成特殊描述符,每个类对应一种消息类型:
class Person(message.Message):
__metaclass__ = reflection.GeneratedProtocolMessageType
class PhoneNumber(message.Message):
__metaclass__ = reflection.GeneratedProtocolMessageType
DESCRIPTOR = _PERSON_PHONENUMBER
DESCRIPTOR = _PERSON
class AddressBook(message.Message):
__metaclass__ = reflection.GeneratedProtocolMessageType
DESCRIPTOR = _ADDRESSBOOK
Person
就好像它将Message
基类的每个字段定义为常规字段一样。例如,
import addressbook_pb2
person = addressbook_pb2.Person()
person.id = 1234
person.name = "John Doe"
person.email = "jdoe@example.com"
phone = person.phones.add()
phone.number = "555-4321"
phone.type = addressbook_pb2.Person.PHONE_TYPE_HOME
请注意,这些分配不仅仅是向通用 Python 对象添加任意新字段。如果尝试分配.proto
文件中未定义的字段,AttributeError
则会引发错误。如果将字段分配给错误类型的值,TypeError
则会引发 a 。此外,在设置字段之前读取字段的值会返回默认值。
person.no_such_field = 1 # raises AttributeError