Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Final classes and member functions #286

Closed
dgoldstein0 opened this issue Sep 28, 2016 · 14 comments · Fixed by python/mypy#5522
Closed

Final classes and member functions #286

dgoldstein0 opened this issue Sep 28, 2016 · 14 comments · Fixed by python/mypy#5522

Comments

@dgoldstein0
Copy link

In some languages (e.g. java) it's possible to declare a class as final so that it can't be subclassed; similarly, it's possible to declare functions as final so that they can't be overridden by sublclasses. It would be great if typing could support this - so that classes could be annotated as final or their functions as final, and then tools like mypy could enforce it.

E.g.

@final
class Foo(object):
  pass

# type error
class Bar(Foo):
  pass

(reposting from python/mypy#2032)

@gvanrossum
Copy link
Member

gvanrossum commented Sep 28, 2016 via email

@dgoldstein0
Copy link
Author

yeah, I really want this for the enforcement use case. I suppose failure
at runtime would work most of the time because classes are usually declared
in module scope but then you need some test that everything can be
imported; I suppose I'd trust a type checker to actually complain about all
usages in the case that I had some code which turned out not to be tested
at all.

On Wed, Sep 28, 2016 at 8:22 AM Guido van Rossum [email protected]
wrote:

It's an interesting idea. In Python's past things like this have been
proposed many times, but usually with the idea that a metaclass would have
to be added that enforces it (like with @abstractmethod). I wonder if
(like that) this should live in the abc module, possibly with an
implementation that enforces it. But the type checker could still enforce
it too. Alternatively it could live in typing and be purely a type checker
feature.

OTOH Wouldn't a comment (plus a note in any docs for the class) be just as
effective? (Not, I guess, if you have an existing class or method that you
want to change to be final -- the @final decorator would allow you to
find any violations quicker than a grep, perhaps, and catch future
violations.)


You are receiving this because you authored the thread.
Reply to this email directly, view it on GitHub
#286 (comment), or mute
the thread
https://github.com/notifications/unsubscribe-auth/ABBnd0gmFYMjGgPL3qSGa-ZKMgLucTfdks5quoYfgaJpZM4KIyCv
.

@gvanrossum
Copy link
Member

gvanrossum commented Sep 28, 2016 via email

@dgoldstein0
Copy link
Author

You have, but at the same time you suggested I come here to too

On Wed, Sep 28, 2016, 3:37 PM Guido van Rossum [email protected]
wrote:

Given the (relative) uniqueness of your use case you're probably better off
hacking your own metaclass. But I believe I've said that before. :-)


You are receiving this because you authored the thread.
Reply to this email directly, view it on GitHub
#286 (comment), or mute
the thread
https://github.com/notifications/unsubscribe-auth/ABBnd00YXV6LLWmPtn0U89d4MzsHk5nTks5quuw0gaJpZM4KIyCv
.

@gvanrossum
Copy link
Member

gvanrossum commented Sep 29, 2016 via email

@dgoldstein0
Copy link
Author

At the moment I'm thinking it's probably easier to write a linter for my
use case. But of course if this became a mypy feature it'd be less code to
just use that.

On Wed, Sep 28, 2016, 7:34 PM Guido van Rossum [email protected]
wrote:

Yeah, so we can gauge interest.


You are receiving this because you authored the thread.
Reply to this email directly, view it on GitHub
#286 (comment), or mute
the thread
https://github.com/notifications/unsubscribe-auth/ABBndy1STLj7yYEzrtbGvLisPrLipAxVks5quyPFgaJpZM4KIyCv
.

@elazarg
Copy link

elazarg commented Jul 27, 2017

final can be used similarly to the sealed feature in Scala, declaring a class to be only inheritable in the current file. this way the type checker can treat it as a Union over the subclasses (e.g. finding dead code, non-exhaustive overload/conditionals, more precise flow-sensitive recommendation etc.), without the need for awkward bookkeeping in a Union. In particular it will be suitable for Expression and Statement in mypy source code.

@JukkaL
Copy link
Contributor

JukkaL commented Jul 27, 2017

@elazarg I agree that the semantics you described would be nice.

@ilevkivskyi ilevkivskyi changed the title [feature request] final classes and member functions Final classes and member functions Jul 27, 2017
@sobolevn
Copy link
Member

sobolevn commented Jul 14, 2018

I have released a utility library that can make classes final: https://github.com/wemake-services/final-class

@AgileInstitute
Copy link

The purpose of the sealed/final keywords is the same as making something in C++ nonvirtual: It allows you to improve the performance of a bottlenecked bit of code. As I understand it, method lookup in Python is always...well, a lookup. No performance benefit. So this run-time check is a rather weird, un-Pythonic hack.

Rather than creating a run-time failure to enforce something, I'd recommend providing the consumers of your class with some clear tests that give examples of how your code is to be properly used. If this is a team you have influence over, then perhaps provide some guidance via lunch-n-learn sessions, pair-programming, or mob-programming on basic OO principles, S.O.L.I.D., patterns wisdom (e.g., "Favor delegation over inheritance" --GoF), and Beck's Four Rules of Simple Design (e.g., "Testability trumps everything else, because everything else is made malleable through tests." -- me, just now).

@elazarg
Copy link

elazarg commented Aug 9, 2018

Final functions and classes allow you to reason better about the behavior of the program, making your program easier to understand. Being able to e.g. inline function calls is just one consequence.

This doesn't mean of course that runtime failures is the right way to implement such feature.

@gvanrossum
Copy link
Member

@AgileInstitute Please stop lecturing.

@AgileInstitute
Copy link

My apologies. Got a little off-track.

ilevkivskyi added a commit to python/mypy that referenced this issue Sep 11, 2018
Fixes #1214
Fixes python/typing#286
Fixes python/typing#242 (partially, other part is out of scope)


This is a working implementation of final access qualifier briefly discussed at PyCon typing meeting. Final names/attributes can be used to have more static guarantees about semantics of some code and can be used by other tools like mypyc for optimizations.

We can play with this implementation before starting to write an actual PEP.

The basic idea is simple: once declared as final, a name/attribute can't be re-assigned, overridden, or redefined in any other way. For example:
```python
from typing import Final

NO: Final = 0
YES: Final = 255

class BaseEngine:
    RATE: Final[float] = 3000

YES = 1  # Error!

class Engine(BaseEngine):
    RATE = 9000  # Also an error!
```
For more use cases, examples, and specification, see the docs patch.

Here are some comments on decisions made:
* __What can be final?__ It is hard to say what semantic nodes are important, I started from just module and class constants, but quickly realized it is hard to draw the line without missing some use cases (in particular for mypyc). So I went ahead and implemented all of them, everything can be final: module constants, class-level and instance-level attributes, method, and also classes.
* __Two names or one name?__ I currently use two names `Final` for assignments and `@final` for decorators. My PEP8-formatted mind just can't accept `@Final` :-)
* __Should re-exported names keep they const-ness?__ I think yes, this is a very common pattern, so it looks like this is a sane default.
* __What to do with instance-level vs class-level attributes?__ The point here is that mypy has a common namespace for class attributes. I didn't want to complicate things (including the mental model), so I just decided that one can't have, e.g., a name that is constant on class but assignable on instances, etc. Such use cases are relatively rare, and we can implement this later if there will be high demand for this.

...deferred features:
* I didn't implement any constant propagation in mypy _yet_. This can be done later on per use-case 
  basis. For example:
  ```python
  fields: Final = [('x', int), ('y', int)]
  NT = NamedTuple('NT', fields)
  ```
* __Should final classes be like sealed in Scala?__ I think probably no. On one hand it could be be a nice feature, on other hand it complicates the mental model and is less useful for things like mypyc.
* I don't allow `Final` in function argument types. One argument is simplicity, another is I didn't see many bugs related to shadowing an argument in function bodies, finally people might have quite different expectations for this. If people will ask, this would be easy to implement.

...and implementation internals:
* There are two additional safety nets that I don't mention in the docs: (a) there can be no `TypeVar`s in the type of class-level constant, (b) instance-level constant can't be accessed on the class object.
* I generate errors for re-definitions in all subclasses, not only in immediate children. I think this is what most people would want: turning something into a constant will flag most re-assignment points.
* We store the `final_value` for constants initialized with a simple literal, but we never use it. This exists only for tools like mypyc that may use it for optimizations.

cc @ambv @rchen152 @vlasovskikh
@NeilGirdhar
Copy link

@dgoldstein0 I've been working on a base class (not a metaclass) that does various related checks. I've considered a final method decorator (https://github.com/NeilGirdhar/ipromise). If this looks interesting to you, please feel free to add a final decorator and check in the base class.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

8 participants