问题描述
我编写了一个类对象来访问来自 unicode 块的数学字母数字符号,如 https://en.wikipedia.org/wiki/Mathematical_Alphanumeric_Symbols 中所述
arrThree
要使用它,我会这样做:
# San-serif
LATIN_SANSERIF_norMAL_UPPER = (120224,120250)
LATIN_SANSERIF_norMAL_LOWER = (120250,120276)
LATIN_SANSERIF_BOLD_UPPER = (120276,120302)
LATIN_SANSERIF_BOLD_LOWER = (120302,120328)
LATIN_SANSERIF_ITALIC_UPPER = (120328,120354)
LATIN_SANSERIF_ITALIC_LOWER = (120354,120380)
LATIN_SANSERIF_BOLDITALIC_UPPER = (120380,120406)
LATIN_SANSERIF_BOLDITALIC_LOWER = (120406,120432)
class MathAlphanumeric:
def __init__(self,script,font,style,case):
self.script = script
self.font = font
self.style = style
self.case = case
def charset(self):
start,end = eval('_'.join([self.script,self.font,self.style,self.case]).upper())
for c in range(start,end):
yield chr(c)
@staticmethod
def supported_scripts():
return {'latin','greek','digits'}
@staticmethod
def supported_fonts():
return {'serif','sanserif','calligraphy','fraktor','monospace','doublestruck'}
@staticmethod
def supported_style():
return {'normal','bold','italic','bold-italic'}
@staticmethod
def supported_case():
return {'upper','lower'}
[输出]:
ma = MathAlphanumeric('latin','lower')
print(list(ma.charset()))
代码按预期工作,但要涵盖所有数学字母数字符号,我必须枚举 ['?','?','?']
编号中的所有开始和结束符号。的常数。
我的问题是:
- 是否有更好的方法来创建所需的
script * fonts * style * case
对象? - 有没有办法避免
MathAlphanumeric
的初始化?的常量,以便script * fonts * style * case
按预期工作? - 在某些 unicode.org 相关库中是否提供了这样的对象或函数?
解决方法
您可能对 unicodedata
标准库特别感兴趣:
-
unicodedata.lookup
:按名称查找字符。如果找到具有给定名称的字符,则返回相应的字符。如果未找到,则引发
KeyError
。 -
unicodedata.name
:以字符串形式返回分配给字符 chr 的名称。
一个简单的例子:
>>> import unicodedata
>>> unicodedata.name(chr(0x1d5a0))
'MATHEMATICAL SANS-SERIF CAPITAL A'
>>> unicodedata.lookup("MATHEMATICAL SANS-SERIF CAPITAL A")
'?'
>>> unicodedata.name(chr(0x1d504))
'MATHEMATICAL FRAKTUR CAPITAL A'
>>> unicodedata.lookup("MATHEMATICAL FRAKTUR CAPITAL A")
'?'
现在您必须找到 unicodedata
期望用于您的用例的所有名称,从中构造相应的字符串,然后调用 lookup
。
这是一个小型的概念验证:
import unicodedata
import string
def charset(script: str,font: str,style: str,case: str):
features = ["MATHEMATICAL"]
# TODO: use script
assert font in MathAlphanumeric.supported_fonts(),f"invalid font {font!r}"
features.append(font.upper())
assert style in MathAlphanumeric.supported_style(),f"invalid style {style!r}"
if style != "normal":
if font == "fraktur":
features.insert(-1,style.upper()) # "bold" must be before "fraktur"
elif font in ("monospace","double-struck"):
pass # it has only one style,and it is implicit
else:
features.append(style.upper())
assert case in MathAlphanumeric.supported_case(),f"invalid case {case!r}"
features.append("CAPITAL" if case == "upper" else "SMALL")
return tuple(unicodedata.lookup(" ".join(features + [letter]),) for letter in string.ascii_uppercase)
if __name__ == '__main__':
print("".join(charset("latin","sans-serif","bold","lower")))
# ??????????????????????????
print("".join(charset("latin","fraktur","upper")))
# ??????????????????????????
print("".join(charset("latin","monospace","double-struck","upper")))
# KeyError: "undefined character name 'MATHEMATICAL DOUBLE-STRUCK CAPITAL C'"
(我稍微改变了你的 supported_fonts
方法:return {'serif','sans-serif','calligraphy','fraktur','monospace','double-struck'}
)
但是在 Unicode 中有很多警告:它包含您可能想要的所有字形,但没有以连贯的方式组织(由于历史原因)。我的例子中的失败是由:
>>> unicodedata.name("?") # the letter copied from the Wikipedia page
'MATHEMATICAL FRAKTUR CAPITAL B'
>>> unicodedata.name("ℭ") # same,but for C
'BLACK-LETTER CAPITAL C'
所以你需要很多特殊情况。
还有:
- 使用
eval
被认为是一种不好的做法(参见 this question),如果您可以避免它,您应该这样做。 - 使用十进制值作为 unicode "characters" 并不方便,我不得不在十六进制之间进行转换,以便将您的代码与维基百科页面进行比较。仅以
0x
为前缀就足以告诉 Python 它是一个十六进制值,但除了看起来“奇怪”之外,它的工作原理完全相同:0x1d5a0 == 120224
为 True。 - 使用一个只有一个方法从实例
__init__
获取其参数的类被认为是一个 smell,你可以让它成为一个函数,更简单和更清晰。如果您想要的是 namespace,则可以改用 Python 模块。 - 支持的脚本、字体、样式和大小写是常量,您可以将它们设为类变量,而不是将它们放在
staticmethod
中。