故事的开始来自今天发现本月5号忘记了leetcode每日一题打卡。为了保证本人的第一次月打卡徽章,我打算补打这个卡。很快我发现这是一道并不困难的“困难”题,因此我决定写一个很Coooooooool的函数,题目是这样的:
给你一个以字符串形式表述的 布尔表达式(boolean) expression,返回该式的运算结果。
有效的表达式需遵循以下约定:
- “t”,运算结果为 True
- “f”,运算结果为 False
- “!(expr)”,运算过程为对内部表达式 expr 进行逻辑 非的运算(NOT)
- “&(expr1,expr2,…)”,运算过程为对 2 个或以上内部表达式 expr1, expr2, … 进行逻辑 与的运算(AND)
- “|(expr1,expr2,…)”,运算过程为对 2 个或以上内部表达式 expr1, expr2, … 进行逻辑 或的运算(OR)
来源:力扣(LeetCode)
链接:https://leetcode.cn/problems/parsing-a-boolean-expression
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
这个是我写的代码。
from collections import deque
DEBUG = False
def boolean(x: str) -> bool or str:
if x not in "tf":
return x
return True if x == "t" else False
class BoolExpression:
def __repr__(self):
return f"A Bool Expression parser"
def cal_symbol_result(self, x1: bool, grammar_symbol: str, x2: bool = None) -> bool:
assert (grammar_symbol in "!|&") # calculate in three types of char
if DEBUG:
print(f"checking {x1}, {x2}")
if grammar_symbol == "!":
return not x1
elif grammar_symbol == "&":
return x1 & x2 if x2 != None else x1
else:
return x1 | x2 if x2 != None else x1
def parse_bool_expr(self, expression: str) -> bool:
# print(expression)
grammar_stack = deque()
char_stack = deque()
for expression_char in expression: # iter through expression char
if DEBUG:
print(f"showing {expression_char}, {grammar_stack}, {char_stack}")
if expression_char in "!&|": # if char is grammar sign
grammar_stack.append(expression_char)
elif expression_char == ")": # or end of the expression
cur_grammar_char = grammar_stack.pop()
cur_variable_char = char_stack.pop()
cur_result = None
while cur_variable_char != "(": # iter through all expression need to be calculate
if DEBUG:
print(f"popping {cur_variable_char}, {cur_grammar_char}")
cur_result = self.cal_symbol_result(cur_variable_char, cur_grammar_char, cur_result)
if DEBUG:
print(f"getting {cur_result}")
cur_variable_char = char_stack.pop()
if DEBUG:
print(f"popping {cur_variable_char}, remaining {char_stack}")
char_stack.append(cur_result)
elif expression_char != ",": # or some other chars
char_stack.append(boolean(expression_char))
# print(char_stack)
assert (len(char_stack) == 1)
return char_stack[0]
其实我一开始是想把boolean这个函数写进类里的,但是为了省去写self.boolean的调用,我绞尽脑汁回想起了曾经见过的一个装饰器@staticmethod。我清晰地记得似乎这个方法可以不用写self,但很快,事实告诉我并非如此,我依然要使用self.boolean去调用。显然我贫瘠的记忆并不足以支持我继续干想,动手试一试的想法很快涌入了我的脑海。那就去试一试学一学吧,我想。
类的方法
因此,今天的内容,就从这里开始。首先我们要提到的是装饰器带来的类方法。凭借我的记忆和一些简单的查找,我找到了三种方法:@property, @classmethod, @staticmethod
class Solution:
"""
This is description which will be showed
The class of solution
"""
def add(self) -> int:
return self.a + self.b
@property
def add1(self) -> int:
return self.a + self.b
@classmethod
def add2_1(cls):
print("add2_1")
@classmethod
def add2(cls) -> str:
print(f"self.instance is {cls.__name__}")
print(cls.a)
return cls.__name__
@staticmethod
def add3(a: int) -> int:
return a + 1
def __repr__(self):
return f"a solution class with {self.a}, {self.b}"
@property
@property 将函数包装为类的属性,这个属性是直接获取属性,使用和类的普通属性完全相同。
In [7]: Solution.add1
Out[7]: <property at 0x2812ab37778>
In [8]: s.add1
Out[8]: 5
个人觉得主要用途是用于在类中创建只读的属性,即将私有属性安全化,例如本例中,如果我们不希望实例对象可以修改 _add1 属性,则可以在不定义 setattr 的情况下将 add1 变为只读属性。
@classmethod
@classmethod 将函数变为类的方法,该方法只能调用类的属性,而不能调用实例的属性,因此无法完成 self.a + self.b 的操作,只能使用类的属性
In [1]: s.add2()
self.instance is Solution
<member 'a' of 'Solution' objects>
Out[1]: 'Solution'
@staticmethod
@staticmethod 确实可以少写self,很遗憾,是在定义的时候,是在定义 add3 时,无需写self,因为它不只是实例的方法,它是类的方法,我们可以随意调用。
In [3]: s.add3(1)
Out[3]: 2
In [4]: Solution.add3(1)
Out[4]: 2
Summary
简单看一下这三个函数的区别:
In [13]: Solution.add
Out[13]: <function __main__.Solution.add(self) -> int>
In [14]: Solution.add1
Out[14]: <property at 0x1cf238e7728>
In [15]: Solution.add2
Out[15]: <bound method Solution.add2 of <class '__main__.Solution'>>
In [16]: Solution.add3
Out[16]: <function __main__.Solution.add3(a: int) -> int>
----------------------------------------------------------------------------------
In [9]: s.add
Out[9]: <bound method Solution.add of a solution class with 2, 3>
In [10]: s.add1
Out[10]: 5
In [11]: s.add2
Out[11]: <bound method Solution.add2 of <class '__main__.Solution'>>
In [12]: s.add3
Out[12]: <function __main__.Solution.add3(a: int) -> int>
类的属性
这一部分我想说的是类的属性,其实也没什么特别难的,可以通过 dir 自己查到类的方法
In [17]: dir(s)
Out[17]:
['__annotations__',
'__class__',
'__delattr__',
'__dir__',
'__doc__',
'__eq__',
'__format__',
'__ge__',
'__getattribute__',
'__gt__',
'__hash__',
'__init__',
'__init_subclass__',
'__le__',
'__lt__',
'__module__',
'__name__',
'__ne__',
'__new__',
'__reduce__',
'__reduce_ex__',
'__repr__',
'__setattr__',
'__sizeof__',
'__slots__',
'__str__',
'__subclasshook__',
'_add1',
'a',
'add',
'add1',
'add2',
'add2_1',
'add3',
'b',
'instance']
这里还是上面这个类,我们添加一些方法的重载。
class Solution:
"""
This is description which will be showed
The class of solution
"""
__name__: str = "Sub solution"
__slots__ = ["a", "b", "_add1"]
instance = 0
def __new__(cls, *args, **kwargs):
if not cls.instance:
cls.instance = super().__new__(cls)
return cls.instance
def __init__(self, a: int, b: int):
self.a = a
self.b = b
self._add1 = 5
def _in_slots(self, attr) -> bool:
for cls in type(self).__mro__:
if attr in getattr(cls, '__slots__', []):
return True
return False
def __setattr__(self, attribute: 'attribute', item: int):
if attribute == "_add1":
print("PLEASE DO NOT CHANGE add1")
return
if attribute == "a" and item > 100:
print(f"self.a do not support assign over 100")
if self._in_slots(attribute):
object.__setattr__(self, attribute, item)
return
# print("SET ERROR, try for subclass")
raise AttributeError(f"Something has gone wrong. {attribute} may not be assigned")
def add(self) -> int:
return self.a + self.b
@property
def add1(self) -> int:
return self.a + self.b
@classmethod
def add2_1(cls):
print("add2_1")
@classmethod
def add2(cls) -> str:
print(f"self.instance is {cls.__name__}")
print(cls.a)
return cls.__name__
@staticmethod
def add3(a: int) -> int:
return a + 1
def __repr__(self):
return f"a solution class with {self.a}, {self.b}"
如果我们这里实现了 __slot__ 那么类将不需要 __dict__ 从而可以节省内存,并且固定类的属性。当事先知道类的属性的时候,可以__slots__来节省内存以及获得更快的属性读取。注意不应当把防止创造__slots__之外的新属性作为使用__slots__的原因,可以使用decorators以及getters,setters来实现属性控制,使类只接受实现的属性。
In [1]: s.a = 101
self.a do not support assign over 100
In [2]: s._add1 = 10
PLEASE DO NOT CHANGE add1
In [3]: s.a = 5
In [4]: s
Out[4]: a solution class with 5, 3
In [5]: s.a = 4
In [6]: s
Out[6]: a solution class with 4, 3
__annotations__
这个属性可以看到类的注释,当然函数也有这个属性。
In [18]: s.__annotations__
Out[18]: {'__name__': str}
In [19]: s.__init__.__annotations__
Out[19]: {'a': int, 'b': int}
__setattr__
实现了该方法后,在对属性赋值时会调用,因此可以在函数里限制属性赋值。
可以禁止不允许的赋值。
__getattribute__
实现该方法可以对类的方法调用进行额外的包装,例如对类的函数调用实现监听,详情见:python给类的所有方法加上装饰器。
class ChiseAipFace(AipFace): #AipFace为百度的人脸识别http封装的类。 def __getattribute__(self, item): """建议对item进行一下判断,不要全局增加""" ret = super().__getattribute__(item) if type( ret) == "<class 'method'>": # 类里面的成员分为三种,method(类方法和实例方法),function(实例方法),int,str...(变量成员),具体需要的时候还是通过type进行判断或者直接通过item来判断 def res(*args, **kwargs): retu = None t = 0 for i in range(10): if i > 0: t = time.time() retu = ret(*args, **kwargs) if retu['error_code'] == 18:#主要解决瞬时并发超限的问题,通过随机值将超限的并发随机在之后的一段时间里面进行接口访问。 time.sleep(random.random() * i * 5) else: if t: logger.warning("接口访问延时18:" + str(time.time() - t) + ",name:" + item) return retu logger.error("接口失败18:" + item) return retu return res else: return ret
__new__ 与 __init__
new是对类的实例化,因此__new__接收 cls 对象,并实例化后返回实例化的对象;而__init__是给实例化的对象赋予属性,新词接受的是 self 实例属性。
这里的__new__主要是用于唯一的对象,即一个类同一时刻只能实例化一个对象(例如音乐播放器或垃圾箱)
Fun Fact
In [2]: type(s)
Out[2]: __main__.Solution
In [3]: type(type(s))
Out[3]: type
In [4]: type.__mro__
Out[4]: (type, object)
In [5]: object.__mro__
Out[5]: (object,)
type 是创建类(包括它本身)的类,而 object 是创建实例的类,为继承的类提供了内置方法。
但是 __mro__ 显示 type 继承了 type 和 object 而 object 继承了 object。