@Pythonetc编译,2019年8月



我的@pythonetc feed中提供了一系列新的Python技巧和编程。

以前的收藏


如果类实例不具有给定名称的属性,则它将尝试访问具有相同名称的类属性。

>>> class A: ... x = 2 ... >>> Ax 2 >>> A().x 2 

实例可以轻松拥有该类不具有的属性,或者具有具有不同值的属性:

 >>> class A: ... x = 2 ... def __init__(self): ... self.x = 3 ... self.y = 4 ... >>> A().x 3 >>> Ax 2 >>> A().y 4 >>> Ay AttributeError: type object 'A' has no attribute 'y' 

如果您希望实例的行为就像没有属性一样,尽管该类具有该属性,则必须创建一个自定义描述符,以禁止从该实例进行访问:

 class ClassOnlyDescriptor: def __init__(self, value): self._value = value self._name = None # see __set_name__ def __get__(self, instance, owner): if instance is not None: raise AttributeError( f'{instance} has no attribute {self._name}' ) return self._value def __set_name__(self, owner, name): self._name = name class_only = ClassOnlyDescriptor class A: x = class_only(2) print(Ax) # 2 A().x # raises AttributeError 

另请参阅classonlymethod Django装饰器的classonlymethodhttps : //github.com/django/django/blob/b709d701303b3877387020c1558a590713b09853/django/utils/decorators.py#L6


在类主体中声明的函数不可用于此类的范围。 这是因为此作用域仅在类创建期间存在。

 >>> class A: ... x = 2 ... def f(): ... print(x) ... f() ... [...] NameError: name 'x' is not defined 

通常这不是问题:方法是在类内部声明的,仅是为了成为方法并在以后调用:

 >>> class A: ... x = 2 ... def f(self): ... print(self.x) ... >>> >>> >>> A().f() 2 

令人惊讶的是,理解也是如此。 它们具有自己的范围,也无法访问类的范围。 就生成器理解而言,这是非常合乎逻辑的:当类已经创建时,将执行其中的代码。

 >>> class A: ... x = 2 ... y = [x for _ in range(5)] ... [...] NameError: name 'x' is not defined 

但是,理解无法获得self 。 提供对x访问的唯一方法是添加另一个范围(是的,愚蠢的解决方案):

 >>> class A: ... x = 2 ... y = (lambda x=x: [x for _ in range(5)])() ... >>> Ay [2, 2, 2, 2, 2] 


在Python中, None等于None ,因此似乎可以使用==检查None

 ES_TAILS = ('s', 'x', 'z', 'ch', 'sh') def make_plural(word, exceptions=None): if exceptions == None: # ← ← ← exceptions = {} if word in exceptions: return exceptions[word] elif any(word.endswith(t) for t in ES_TAILS): return word + 'es' elif word.endswith('y'): return word[0:-1] + 'ies' else: return word + 's' exceptions = dict( mouse='mice', ) print(make_plural('python')) print(make_plural('bash')) print(make_plural('ruby')) print(make_plural('mouse', exceptions=exceptions)) 

但这将是一个错误。 是的, None等于None ,不仅如此。 自定义对象也可以是None

 >>> class A: ... def __eq__(self, other): ... return True ... >>> A() == None True >>> A() is None False 

None比较的唯一正确方法是使用is None



Python中的浮点数可以具有NaN值。 例如,可以使用math.nan获得此math.nannan不等于任何事物,包括自身:

 >>> math.nan == math.nan False 

此外,NaN对象不是唯一的;您可以从不同来源获得几个不同的NaN对象:

 >>> float('nan') nan >>> float('nan') is float('nan') False 

这意味着,通常,您不能将NaN用作字典键:

 >>> d = {} >>> d[float('nan')] = 1 >>> d[float('nan')] = 2 >>> d {nan: 1, nan: 2} 


typing允许您定义生成器的类型。 另外,您可以指定生成哪种类型,将哪种类型传递给生成器以及使用return哪种类型。 例如, Generator[int, None, bool]生成整数,返回布尔值,并且不支持g.send()

但是这个例子更加复杂。 chain_while来自其他生成器的数据,直到其中一个根据condition函数返回作为停止信号的值为止:

 from typing import Generator, Callable, Iterable, TypeVar Y = TypeVar('Y') S = TypeVar('S') R = TypeVar('R') def chain_while( iterables: Iterable[Generator[Y, S, R]], condition: Callable[[R], bool], ) -> Generator[Y, S, None]: for it in iterables: result = yield from it if not condition(result): break def r(x: int) -> Generator[int, None, bool]: yield from range(x) return x % 2 == 1 print(list(chain_while( [ r(5), r(4), r(3), ], lambda x: x is True, ))) 


为工厂方法设置注释并不像看起来那样容易。 只想使用这样的东西:

 class A: @classmethod def create(cls) -> 'A': return cls() 

但这是错误的。 诀窍是create返回的不是A ,而是返回的cls ,它是A 或其后代之一。 看一下代码:

 class A: @classmethod def create(cls) -> 'A': return cls() class B(A): @classmethod def create(cls) -> 'B': return super().create() 

mypy检查的结果是错误error: Incompatible return value type (got "A", expected "B") 。 再一次,问题是super().create()注释为返回A ,尽管在这种情况下它返回B

如果使用TypeVar注释cls则可以解决此TypeVar

 AType = TypeVar('AType') BType = TypeVar('BType') class A: @classmethod def create(cls: Type[AType]) -> AType: return cls() class B(A): @classmethod def create(cls: Type[BType]) -> BType: return super().create() 

现在, create将返回cls类的实例。 但是,这些注释太含糊,我们丢失了clsA的子类型的信息:

 AType = TypeVar('AType') class A: DATA = 42 @classmethod def create(cls: Type[AType]) -> AType: print(cls.DATA) return cls() 

我们"Type[AType]" has no attribute "DATA"错误"Type[AType]" has no attribute "DATA"

要解决此问题,必须将AType明确定义为A的子类型A 为此, TypeVar带有bound参数的TypeVar

 AType = TypeVar('AType', bound='A') BType = TypeVar('BType', bound='B') class A: DATA = 42 @classmethod def create(cls: Type[AType]) -> AType: print(cls.DATA) return cls() class B(A): @classmethod def create(cls: Type[BType]) -> BType: return super().create() 

Source: https://habr.com/ru/post/zh-CN466315/


All Articles