********频道_来自我的********频道@pythonetc的提示和技巧,2019年8月
********频道
It is a new selection of tips and tricks about Python and programming from my ********-channel @pythonetc.
这是我的********频道@pythonetc中有关Python和编程的一些新技巧和窍门。
← 以前的出版物
If an instance of a class doesn’t have an attribute with the given name, it tries to access the class attribute with the same name.
如果类的实例没有具有给定名称的属性,则它将尝试访问具有相同名称的类属性。
>>> class A:
... x = 2
...
>>> A.x
2
>>> A().x
2
It’s fairly simple for an instance to have attribute that a class doesn’t or have the attribute with the different value:
实例具有类没有的属性或具有不同值的属性非常简单:
>>> class A:
... x = 2
... def __init__(self):
... self.x = 3
... self.y = 4
...
>>> A().x
3
>>> A.x
2
>>> A().y
4
>>> A.y
AttributeError: type object 'A' has no attribute 'y'
If it’s not that simple, however, if you want an instance behave like it doesn’t have an attribute despite the class having it. To make it happen you have to create custom descriptor that doesn’t allow access from the instance:
如果不是那么简单,那么,如果您希望实例的行为就像它没有一个属性,尽管该类具有该属性。 为此,您必须创建不允许从实例访问的自定义描述符:
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(A.x) # 2
A().x # raises AttributeError
See also how the Django classonlymethod
decorator works: https://github.com/django/django/blob/b709d701303b3877387020c1558a590713b09853/django/utils/decorators.py#L6
另请参阅Django classonlymethod
装饰器的工作方式: https : //github.com/django/django/blob/b709d701303b3877387020c1558a590713b09853/django/utils/decorators.py#L6
Functions declared in a class body can’t see the class scope. It makes sense since the class scope only exists during class creation.
在类主体中声明的函数看不到类范围。 这是有道理的,因为类作用域仅在类创建期间存在。
>>> class A:
... x = 2
... def f():
... print(x)
... f()
...
[...]
NameError: name 'x' is not defined
That is usually not a problem: methods are declared inside a class only to become methods and be called later:
通常这不是问题:方法在类内部声明只是成为方法,以后再调用:
>>> class A:
... x = 2
... def f(self):
... print(self.x)
...
>>>
>>>
>>> A().f()
2
Somewhat surprisingly, the same is true for comprehensions. They have their own scopes and can’t access the class scope as well. That really make sense for generator comprehensions: they evaluate expressions after the class creation is already finished.
令人惊讶的是,对于理解也是如此。 它们具有自己的作用域,也无法访问类作用域。 这对于生成器理解确实很有意义:它们在类创建完成之后对表达式进行求值。
>>> class A:
... x = 2
... y = [x for _ in range(5)]
...
[...]
NameError: name 'x' is not defined
Comprehensions, however, have no access to self
. The only way to make it work is to add one more scope (yep, that’s ugly):
然而,理解无法获得self
。 使它起作用的唯一方法是添加一个范围(是的,这很丑):
>>> class A:
... x = 2
... y = (lambda x=x: [x for _ in range(5)])()
...
>>> A.y
[2, 2, 2, 2, 2]
In Python, None
is equal to None
so it looks like you can check for None
with ==
:
在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))
This is a wrong thing to do though. None
is indeed is equal to None
, but it’s not the only thing that is. Custom objects may be equal to None
too:
这是错误的做法。 None
一个确实等于None
,但这不是唯一的事情。 自定义对象也可能等于“ None
:
>>> class A:
... def __eq__(self, other):
... return True
...
>>> A() == None
True
>>> A() is None
False
The only proper way to compare with None
is to use is None
.
与None
比较的唯一正确方法是使用is None
。
Python floats can have NaN values. You can get one with math.nan
. nan
is not equal to anything including itself:
Python浮点数可以具有NaN值。 您可以使用math.nan
获得一个。 nan
不等于任何事物,包括自身:
>>> math.nan == math.nan
False
Also, NaN object is not unique, you can have several different NaN objects from different sources:
此外,NaN对象不是唯一的,您可以从不同来源获得几个不同的NaN对象:
>>> float('nan')
nan
>>> float('nan') is float('nan')
False
That means that you generally can’t use NaN as a dictionary key:
这意味着您通常不能将NaN用作字典键:
>>> d = {}
>>> d[float('nan')] = 1
>>> d[float('nan')] = 2
>>> d
{nan: 1, nan: 2}
typing
allows you to define type for generators. You can additionally specify what type is yielded, what type can be sent into a generator and what is returned.
typing
允许您定义生成器的类型。 您还可以指定产生什么类型,可以将什么类型发送到生成器以及返回什么类型。
Generator[int, None, bool]
is a generator that yields integers, returns boolean value and doesn’t support
Generator[int, None, bool]
是生成整数,返回布尔值且不支持
g.send()
.
g.send()
。
Here is slightly more complicated example. chain_while
yields from other generators until one of them returns something that is a signal to stop according to the condition
function:
这是稍微复杂的示例。 chain_while
从其他生成器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,
)))
Annotating a factory method is not as simple as it may seem. The immediate urge is to use something like this:
注释工厂方法并不像看起来那样简单。 紧迫的要求是使用这样的东西:
class A:
@classmethod
def create(cls) -> 'A':
return cls()
However, that is not a right thing to do. The catch is, create
doesn’t return A
, it returns an instance of cls that is A
or any of its descendants. Look at this code:
但是,这不是正确的事情。 问题是, create
不返回A
,它返回的是cl实例A
或其任何后代。 看下面的代码:
class A:
@classmethod
def create(cls) -> 'A':
return cls()
class B(A):
@classmethod
def create(cls) -> 'B':
return super().create()
The mypy check result is error: Incompatible return value type (got "A", expected "B")
. Again, the problem is super().create()
is annotated to return A
while it clearly returns B
in this case.
mypy检查结果为error: Incompatible return value type (got "A", expected "B")
。 同样,问题是super().create()
被注释为返回A
而在这种情况下显然返回B
You can fix that by annotating cls with TypeVar:
您可以通过用TypeVar注释cls来解决此问题:
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()
Now create
returns the instance of the cls
class. However, this annotations are too loose, we lost the information that cls
is a subtype of A
:
现在, create
返回cls
类的实例。 但是,此注释过于宽松,我们丢失了cls
是A
的子类型的信息:
AType = TypeVar('AType')
class A:
DATA = 42
@classmethod
def create(cls: Type[AType]) -> AType:
print(cls.DATA)
return cls()
The error is "Type[AType]" has no attribute "DATA"
.
错误是"Type[AType]" has no attribute "DATA"
。
To fix that you have to explicitly define AType
as a subtype of A
with the bound
argument of TypeVar
:
要解决此问题,您必须使用AType
的bound
参数将TypeVar
明确定义为A
的子类型:
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()
********频道